How Java (Java8) fix WhiteHat "Improper Certificate Validation" (CWE-295) security vulnerability - security

We use the WhiteHat Source scanner to scan our source code. The tool finds out 'Improper Certificate Validation' (CWE-295) security issue at 2 methods. Is it a True Positive security issue? If yes, how could we fix it in Java 8, do we have a solution to fix issue like this? Thank you very much.
public void checkClientTrusted(X509Certificate[] certs, String authType) --> security vuln
public void checkServerTrusted(X509Certificate[] certs, String authType) --> security vuln
// http://www.nakov.com/blog/2009/07/16/disable-certificate-validation-in-java-ssl-connections/
public class JavaCertificationUtils {
private static final SanitizedLogger LOG = new SanitizedLogger(JavaCertificationUtils.class);
public static void javaTrustAllCerts() {
try {
// Create a trust manager that does not validate certificate chains
TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
}};
// Install the all-trusting trust manager
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
// Create all-trusting host name verifier
HostnameVerifier allHostsValid = new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
// Install the all-trusting host verifier
HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
} catch (Exception e) {
LOG.error("Java Certificate All Certs Exception.", e);
}
}
}

The code you are showing does two things:
It disables the TLS certificate chain validation (with trustAllCerts).
And than it disables the host name verification (with allHostsValid).
How to fix this? First of all - remove the code. All of it. The exact lines that seem to do the damage are:
sc.init(null, trustAllCerts, new java.security.SecureRandom());
HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
You will probably notice (with a probability of 90%), that the code will stop working. This might be due to different reasons.
You are trying to connect to a service that uses a self signed certificate (might be a reason someone made a hack like this in the first place). The service would need to fix this.
You don't have all necessary certificates in the trust store to verify the trust chain. In this case you would need to extend your trust store and add the necessary Root CA certificates.
You connect over a SSH tunnel to a TLS secured service. Once you translate a proper domain name to a localhost: the TLS hostname validation will fail.
Something else will fail.
Let us know what the error is then.

Related

SSL public key pinning is not working with HttpClientHandler for Xamarin.iOS, can still be eavesdropped. How do I secure the app against MITM attacks?

I am new to Xamarin Forms and also SSL Pinning. I am looking at an issue regarding SSL pinning in a preexisting app at work.
The idea is that, with server certificate (or public key) pinned, the app should close when a proxy (middle man) is connected.
I have checked many tutorials and have tried many solutions but the app seems to work normally confirming the public key even with proxy setup.
Attempt 1:
The code that was given to me is below and it uses ServerCertificateCustomValidationCallback and HttpClientHandler. I learnt that the ValidateRemoteCertificate method has the details of the server certificate, and the chain of certs associated to the remote cert (server).
https://learn.microsoft.com/en-gb/dotnet/api/system.net.security.remotecertificatevalidationcallback?view=net-7.0
// Main.cs
const string MyPublicKey = "YOUR_PUBLIC_KEY_STRING"
public async Task<HttpContent> CheckCertificate()
{
var destUri = new Uri("YOUR_URL");
var handler = new HttpClientHandler();
handler.ServerCertificateCustomValidationCallback = ValidateRemoteCertificate;
using (HttpClient client = new HttpClient(handler))
{
using (HttpResponseMessage result = await client.GetAsync(destUri))
{
return result.Content;
}
}
}
public bool ValidateRemoteCertificate(object sender,
X509Certificate cert,
X509Chain chain,
SslPolicyErrors policyErrors)
{
byte[] foundCert = null;
if (chain != null)
{
if (chain.ChainElements != null && chain.ChainElements.Count > 0)
{
if (chain.ChainElements[1].Certificate != null)
{
foundCert = chain.ChainElements[1].Certificate.RawData;
}
}
}
if (foundCert != null)
{
var key = cert.GetPublicKeyString();
if (!MyPublicKey.Equals(key))
{
// PublicKey mismatch, Exiting...
CloseApp();
return false;
}
else
{
// "Public key matches
}
return true;
}
return false;
}
This returns an exception with "Certificate Unknown". I found that the chain is not null, but ChainElements is empty.
I read many articles about certificate pinning and added the following code in Info.plist.
<key>NSAppTransportSecurity</key>
<dict>
<key>NSPinnedDomains</key>
<dict>
<key>YOUR_URL_HERE</key>
<dict>
<key>NSIncludesSubdomains</key>
<true/>
<key>NSPinnedCAIdentities</key>
<array>
<dict>
<key>SPKI-SHA256-BASE64</key>
<string>YOUR_KEY</string>
</dict>
</array>
</dict>
</dict>
</dict>
And I receive an exception that certificate is not trusted. I assume that this code in info.plist expects a trusted CA relating to this certificate to be trusted by the device owner. Which is not the target of this task.
Attempt 2:
I decided to just use public key pinning instead of certificate. So I changed ValidateRemoteCertificate code like this:
var key = cert.GetPublicKeyString();
if (!MyPublicKey.Equals(key))
{
// PublicKey mismatch, Exiting...
CloseApp();
return false;
}
else
{
// "Public key matches
}
return true;
And I removed info.plist changes. This allows the app to run smoothly. But the problem is, when a proxy is connected, it still works, meaning the cert still comes with the correct public key.
I am trying to figure out if I am missing something in this implementation or if the methods used here is even valid. I checked how it is handled in Android, I see the exact same implementation as my first code block, and with proxy, the app fails to launch (handshake failed; Trust anchor for certification path not found).
If this is an iOS issue, is there any other form of implementation that I need to use? Or is there a better way to pin cert (perhaps bundling the cert with the app)?
Update 1:
I have analysed the certificate chain validation a little further and observed these:
As mentioned in my Attempt 1, the chain received in ValidateRemoteCertificate method is empty (with or without proxy). Someone suggested that to build the chain using the certificate if this happened, in order to validate the chain.
I attempted to do "Build the chain" using this code.
chain.Build(cert))
But I get an error "operation not permitted on this platform" suggesting that "chain.Build" doesn't work for Xamarin.iOS.
Update 2:
I did manage to get the chain built using the following code and everything seems to work normally. But the public key also matches when a proxy is connected and is able to crawl iOS traffic successfully. Hence, making the app function normally in case of man in the middle.
public bool ValidateRemoteCertificate(object sender,
X509Certificate cert,
X509Chain chain,
SslPolicyErrors policyErrors)
{
if (policyErrors == SslPolicyErrors.None)
{
String actualPKString = cert.GetPublicKeyString();
// validate public key
if (!MyPublicKey.SequenceEqual(actualPKString))
{
Console.WriteLine("Security: public key mismatched.");
CloseApp();
return false;
}
//validate chain
if (chain != null)
{
if (!(chain.ChainElements != null && chain.ChainElements.Count > 0))
{
try
{
chain.Build(new X509Certificate2(cert));
Console.WriteLine("Security: built chain");
} catch(Exception e)
{
Console.WriteLine("Security: issues building chain {0}", e);
}
}
if (chain.ChainElements == null || chain.ChainElements.Count == 0)
{
Console.WriteLine("Security: Chain elements still null");
CloseApp();
return false;
}
}
return true;
}
Console.WriteLine("Security: error occured");
CloseApp();
return false;
}

How to list unique Subject Alternative Names from a Java TrustStore (JKS) file

Is there any simple way (preferably from Command Line Interface) to list the unique Subject Alternative Names for all the certificates inside a Java TrustStore (JKS) file?
As a Java developer a small Java program can do the trick:
public static void main(String[] args) {
String fileName= "website_certs.jks";
char[] password = "".toCharArray();
try {
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream(fileName), password);
Set<Object> subjAltNames = Collections.list(ks.aliases()).stream().flatMap(alias -> {
try {
return ((X509Certificate) ks.getCertificate(alias)).getSubjectAlternativeNames().stream();
} catch (Exception e) {
return Stream.empty();
}
}).collect(Collectors.toSet());
subjAltNames.forEach(System.out::println);
} catch (Exception e) {
e.printStackTrace();
}
}
The only thing that is strange in your question is that a trust store usually contains root or intermediate CA certificates. But only leaf certificates installed on a web server have a subject alternative name. Therefore this code only works for trust stores that contains leaf/server certificates.

"error": "invalid_client" from custom OWIN implementation

I am implementing OWIN authentication on a mysql backend, I dont thnk thats a problem as my registration work pretty well. I have basically worked off this post (i.e. nicked most of the code).
I am also using DI via autofac so I have changed a few things around to inject dependencies into the SimpleAuthorizationServerProvider
THE PROBLEM
I post grant_type=password, username and password to http://localhost/myappurl/token and I get back "error":"invalid_client". I get no hits when I try to debug so its probably failing in the library and not getting to my own code. Does anyone know why this would be?
Please pardon the lengthy code, I have no idea where the issue could be so I have posted everything I think is relevant, if anyone needs to see more code, please ask.
SimpleAuthorizationServerProvider
public class SimpleAuthorizationServerProvider : OAuthAuthorizationServerProvider
{
private readonly IUserService _userService;
public SimpleAuthorizationServerProvider(IUserService userService)
{
_userService = userService;
}
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
var authenticate = await _userService.FindUser(context.UserName, context.Password);
if (!authenticate)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim("sub", context.UserName));
identity.AddClaim(new Claim("role", "user"));
context.Validated(identity);
}
}
Startup
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
ConfigureOAuth(app, (IOAuthAuthorizationServerProvider)config.DependencyResolver.GetService(typeof(IOAuthAuthorizationServerProvider)));
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseWebApi(config);
}
public void ConfigureOAuth(IAppBuilder app, IOAuthAuthorizationServerProvider provider)
{
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(90),
Provider = provider,
ApplicationCanDisplayErrors=true,
};
app.UseOAuthAuthorizationServer(OAuthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
}
IocConfig
public static class IocConfig
{
public static void Register(HttpConfiguration config)
{
var builder = new ContainerBuilder();
// Configure the container
// Register individual components
builder.Register(c => new MySQLContext()).As<IMySqlContext>().InstancePerRequest();
builder.RegisterType<SimpleAuthorizationServerProvider>().As<IOAuthAuthorizationServerProvider>();
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
var container = builder.Build();
config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
}
}
You have a lot of code there, so it's not easy to isolate the problem. As a first step, consider removing the code for Autofac DI and see if that makes any difference. It's hard to tell what the problem might be otherwise.
If the issue is indeed related to the DI code, then perhaps this should be a raised as a separate question. In that case, try to create a small code example that demonstrates the issue succinctly. People are more likely to help if the problem code is short and to the point.
Make sure that you've set up SSL for your site. I had a similar issue and the problem was that I was not using SSL.

JBoss AS 7 security: how to get currently logged username?

I am in a Jboss AS 7 environment. My application's /admIn/* path is protected by a security-constraint which requires form based authentication. Security domain is database backed.
It's ok but now I want to display a "good morning " in each page's header.
I'm looking for some sort of getLoggedUsername() or getPrincipal() function but I can't find it.
Please post a reference to the official docs if any.
Thank you.
You should be able to use JAAS. Which is what JBoss 7 ought to be using.
The calling principal will be stored in a SessionContext which you can obtain by telling JBoss it's a resource.
#Resource
private SessionContext context;
public void myAwesomeMethod() {
String currentUser = context.getCallerPrincipal().getName();
}
If for some reason the Injection doesn't work on a Stateless bean, you can look up the EJBContext direct.
#Stateless
public class HelloBean implements com.foo.ejb.HelloRemote {
public void hello() {
try {
InitialContext ic = new InitialContext();
SessionContext sctxLookup =
(SessionContext) ic.lookup("java:comp/EJBContext");
System.out.println("look up EJBContext by standard name: " + sctxLookup);
} catch (NamingException ex) {
throw new IllegalStateException(ex);
}
}
}
This snippet was obtained from 4 ways to obtain EJBContext.

How can I limit login attempts in Spring Security?

Is there some configuration or available module in Spring Security to limit login attempts (ideally, I'd like to have an increasing wait time between subsequent failed attempts)? If not, which part of the API should be used for this?
From Spring 4.2 upwards annotation based event listeners are available:
#Component
public class AuthenticationEventListener {
#EventListener
public void authenticationFailed(AuthenticationFailureBadCredentialsEvent event) {
String username = (String) event.getAuthentication().getPrincipal();
// update the failed login count for the user
// ...
}
}
Implement an AuthenticationFailureHandler that updates a count/time in the DB. I wouldn't count on using the session because the attacker is not going to be sending cookies anyway.
I recently implemented a similar functionality to monitor login failures using JMX. Please see the code in my answer to question Publish JMX notifications in using Spring without NotificationPublisherAware. An aspect on the authenticate method of authentication provider updates MBean and works with a notification listener (code not shown in that question) to block user and IP, send alert emails and even suspend the login if failures exceed a threshold.
Edit
Similar to my answer to question Spring security 3 : Save informations about authentification in database, I think that capturing an authentication failure event (as opposed to customizing a handler) and storing information in database will also work and it will keep the code decoupled as well.
As suggested by Rob Winch in http://forum.springsource.org/showthread.php?108640-Login-attempts-Spring-security, I just subclassed DaoAuthenticationProvider (which could also have been done using an aspect as Ritesh suggests) to limit the number of failed logins, but you could also assert pre-conditions as well:
public class LimitingDaoAuthenticationProvider extends DaoAuthenticationProvider {
#Autowired
private UserService userService;
#Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
// Could assert pre-conditions here, e.g. rate-limiting
// and throw a custom AuthenticationException if necessary
try {
return super.authenticate(authentication);
} catch (BadCredentialsException e) {
// Will throw a custom exception if too many failed logins have occurred
userService.recordLoginFailure(authentication);
throw e;
}
}
}
In Spring config XML, simply reference this bean:
<beans id="authenticationProvider"
class="mypackage.LimitingDaoAuthenticationProvider"
p:userDetailsService-ref="userDetailsService"
p:passwordEncoder-ref="passwordEncoder"/>
<security:authentication-manager>
<security:authentication-provider ref="authenticationProvider"/>
</security:authentication-manager>
Note that I think that solutions which rely on accessing an AuthenticationException's authentication or extraInformation properties (such as implementing an AuthenticationFailureHandler) should probably not be used because those properties are now deprecated (in Spring Security 3.1 at least).
You could also use a service which implements ApplicationListener<AuthenticationFailureBadCredentialsEvent> to update the record in DB.
See spring application events.
Here is my implementation, hope help.
Create a table to store any invalid login attempts.
If invalid attempts > max allowed, set UserDetail.accountNonLocked to false
Spring Security will handle the "lock process" for you. (refer to AbstractUserDetailsAuthenticationProvider)
Last, extends DaoAuthenticationProvider, and integrate the logic inside.
#Component("authenticationProvider")
public class YourAuthenticationProvider extends DaoAuthenticationProvider {
#Autowired
UserAttemptsDao userAttemptsDao;
#Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
try {
Authentication auth = super.authenticate(authentication);
//if corrent password, reset the user_attempts
userAttemptsDao.resetFailAttempts(authentication.getName());
return auth;
} catch (BadCredentialsException e) {
//invalid login, update user_attempts, set attempts+1
userAttemptsDao.updateFailAttempts(authentication.getName());
throw e;
}
}
}
For full source code and implementation, please refer to this - Spring Security limit login attempts example,
create a table to store the values of failed attempts ex : user_attempts
Write custom event listener
#Component("authenticationEventListner")
public class AuthenticationEventListener
implements AuthenticationEventPublisher
{
#Autowired
UserAttemptsServices userAttemptsService;
#Autowired
UserService userService;
private static final int MAX_ATTEMPTS = 3;
static final Logger logger = LoggerFactory.getLogger(AuthenticationEventListener.class);
#Override
public void publishAuthenticationSuccess(Authentication authentication) {
logger.info("User has been logged in Successfully :" +authentication.getName());
userAttemptsService.resetFailAttempts(authentication.getName());
}
#Override
public void publishAuthenticationFailure(AuthenticationException exception, Authentication authentication) {
logger.info("User Login failed :" +authentication.getName());
String username = authentication.getName().toString();
UserAttempts userAttempt = userAttemptsService.getUserAttempts(username);
User userExists = userService.findBySSO(username);
int attempts = 0;
String error = "";
String lastAttempted = "";
if (userAttempt == null) {
if(userExists !=null ){
userAttemptsService.insertFailAttempts(username); }
} else {
attempts = userAttempt.getAttempts();
lastAttempted = userAttempt.getLastModified();
userAttemptsService.updateFailAttempts(username, attempts);
if (attempts + 1 >= MAX_ATTEMPTS) {
error = "User account is locked! <br>Username : "
+ username+ "<br>Last Attempted on : " + lastAttempted;
throw new LockedException(error);
}
}
throw new BadCredentialsException("Invalid User Name and Password");
}
}
3.Security Configuration
1) #Autowired
#Qualifier("authenticationEventListner")
AuthenticationEventListener authenticationEventListner;
2) #Bean
public AuthenticationEventPublisher authenticationListener() {
return new AuthenticationEventListener();
}
3) #Autowired
public void
configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
//configuring custom user details service
auth.authenticationProvider(authenticationProvider);
// configuring login success and failure event listener
auth.authenticationEventPublisher(authenticationEventListner);
}

Resources