Skip to Content
Technical Articles
Author's profile photo Ariel Bravo Ayala

CPI Credentials: Have you been pwned?

The handling of passwords and credentials is something that in my opinion can be improved at CPI. Some time ago I pointed out my concerns about how passwords can be easily obtained, for those with a developer profile. In this post, I won’t talk about these points, but I will use the current password management features to help to verify the strength or weakness of passwords stored in a CPI tenant.

Due to different security breaches, many users have left exposed their passwords for accounts used on different websites. The Institute of one of the founders of SAP (Hasso Plattner Institute) has a tool to validate these security breaches. As a secondary effect to these breaches, it has been possible to obtain lists of passwords and hashes that have given way to dictionaries of attack. A widely discussed good practice is that each password used is unique and that is not found in these dictionaries of passwords. But, how to validate that my passwords have not been leaked? In one hand Troy Hunt helps us, through a public domain API, and on the other hand, I present you an iFlow that uses this API and does the check for you.

About the “Have I been pwned?” API (HIBP)

The API to verify a password, only receives the first 5 characters of the hash (SHA1) of a password. The reason is clear: we don’t want to transmit the complete hash that could evidence the password. Since only 5 characters are sent, the result is a set that consists of partial hashes that share the prefix by which the request is made and the number of times that hash/password has been exposed in the different security breaches. The API has a limit of 1 call every 1.5 seconds.

About the iFlow

The iFlow simply consults the full list of existing credentials in the tenant, using CPI’s API odata. A script then generates the hash of each password, calls the API and finally compares the result. In the end, a simple web page is presented showing the potentially compromised passwords. Due to the limitation of one call every 1.5 seconds and the time needed to generate and compare the hash, each credential takes approximately 2 seconds to be analyzed. So be patient when executing the iflow, as the process could take a while if you have a lot of credentials.

To run the iFlow, open your web browser and use the url:
https://TENANT_DETAILS/http/amba/pwnage-cheker. You will require a proper role to use the oData API and send messages.

A Sample Result:

If you want to implement the iflow by yourself, you can use this snippet as a reference:

// Pwnage-Checker-For-CPI  //
// Ariel Bravo Ayala       //
// 2019                    //
// MIT License             //

import groovy.json.JsonSlurper
import groovy.time.*

def Message processData(Message message) {
    def body = message.getBody(String)
    def secureStorageService =  ITApiFactory.getApi(SecureStoreService.class, null)
    def jsonSlurper = new JsonSlurper()
    def bodyObject = jsonSlurper.parseText(body)
    def output = new StringBuilder()
    def timeStart = new Date()

    output <<= '<html><head><title>CPI Pwnage Checker</title></head><body><h1>Your pwned credentials</h1>'
    output <<= '<table border="1"><tr><th>Kind</th><th>Credential</th><th>Username</th><th>Times compromised</th></tr>'

    bodyObject.d.results.each {
        def credentialDetails = it
        if (["default","successfactors","secure_param"].contains(credentialDetails.Kind)){
            String[] content
            def credential = secureStorageService.getUserCredential(credentialDetails.Name)
            def user = credential.getUsername().toString()
            def pass = credential.getPassword().toString()
            def hash = getSHA1(pass)
            def prefix = hash.substring(0,5)
            def suffix = hash.substring(5)

            def get = new URL(""+prefix).openConnection();
            get.setRequestProperty("User-Agent", "Pwnage-Checker-For-CPI") //Required
            def getRC = get.getResponseCode()
            TimeDuration duration = TimeCategory.minus(new Date(), timeStart)
            sleep(1500 - duration.getMillis()) // Wait between each call
            timeStart = new Date()
            if(getRC.equals(200)) {
                def reader = new BufferedReader(new InputStreamReader(get.getInputStream()))
                reader.eachLine {
                    content = it.split(":")
                    if (content[0] == suffix){
                        output <<= 
                            '<tr><td>' + credentialDetails.Kind +'</td>'+
                            '<td>' + credentialDetails.Name +'</td>'+
                            '<td>' + user +'</td>'+
                            '<td>' + content[1] +'</td></tr>'

    output <<= '</table><br><br>Copyright  2019 - Ariel Bravo Ayala - MIT License' 
    message.setHeader("Content-Type","text/html; charset=utf-8")
    return message

def String getSHA1(String password){
    MessageDigest digest = MessageDigest.getInstance("SHA-1")
    byte[] passwordDigest = digest.digest()
    String hexString = passwordDigest.collect { String.format('%02x', it) }.join()
    return hexString.toUpperCase()

A note of caution: clearly the code can be easily altered to read all the credentials of the system. Have good judgment.

The vulnerabilities mentioned, as well as the limitations of an authentication process based only on user and password, generates the need for other alternatives to be considered by companies. Fortunately, CPI has some interesting options for authentication and authorization such as OAuth, SSO and client certificates. It is worth proposing these options in the design stage of new integrations.

Download the tool Here: Pwnage-Checker-For-CPI
Source: GitHub


Ariel Bravo Ayala

Assigned Tags

      You must be Logged on to comment or reply to a post.
      Author's profile photo Rashmi Joshi
      Rashmi Joshi

      Hi Ariel Bravo Ayala,


      I tried to replicate the same in my trial account but getting below error while trying to perform GET.

      Error Details An internal server error occured: HTTP operation failed invoking$format=json with statusCode: 403. The MPL ID for the failed message is : AF8MOEYPHhn1vEKAyuMdfSEbZSMn For more details please check tail log.
      Please help to resolve the error.
      Author's profile photo Ariel Bravo Ayala
      Ariel Bravo Ayala
      Blog Post Author

      Hi Rashmi, this was created in Neo and using a trick to reuse your authentication. I have not tested this in CF.

      My first attempt would be to check if your user has authorisation to consume the odata api. You can create an oauth client as well and change the channel accordingly. I will try to check.

      Br, Ariel