Category: Coding

Using Memcached in PHP

Quick PHP code used as a proof of concept for storing credentials in memcached — cred is encrypted using libsodium before being send to memcached, and it is decrypted after being retrieved. This is done both to prevent in-memory data from being meaningful and because the PHP memcached extension doesn’t seem to support SSL communication.

<?php

# To generate key and nonce, use sodium_bin2hex to stash these two values
#$sodiumKey   = random_bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES); // 256 bit
#$sodiumNonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES); // 24 bytes

# Stashed key and nonce strings
$strSodiumKey = 'cdce35b57cdb25032e68eb14a33c8252507ae6ab1627c1c7fcc420894697bf3e';
$strSodiumNonce = '652e16224e38da20ea818a92feb9b927d756ade085d75dab';
# Turn key and nonce back into binary data
$sodiumKey = sodium_hex2bin($strSodiumKey);
$sodiumNonce = sodium_hex2bin($strSodiumNonce);

# Initiate memcached object and add sandbox server
$memcacheD = new Memcached;
$memcacheD->addServer('127.0.0.1','11211',1);  # add high priority weight server added to memcacheD

$arrayDataToStore = array(
    "credValueGoesHere",
        "cred2",
        "cred3",
        "cred4",
        "cred5"
);

# Encrypt and stash data
for($i = 0; $i < count($arrayDataToStore); $i++){
    usleep(100);
    $strValue = $arrayDataToStore[$i];
    $strMemcachedKey = 'credtest' . $i;
        $strCryptedValue =  base64_encode(sodium_crypto_secretbox($strValue, $sodiumNonce, $sodiumKey));
    $memcacheD->set($strMemcachedKey, $strCryptedValue,time()+120);
}

# Get each key and decrypt it
for($i = 0; $i < count($arrayDataToStore); $i++){
        $strMemcachedKey = 'credtest'.$i;
        $strValue = sodium_crypto_secretbox_open(base64_decode($memcacheD->get($strMemcachedKey)),$sodiumNonce, $sodiumKey);
        echo "The value on key $strMemcachedKey is: $strValue \n";
}
?>

Updating git on Windows

There’s a convenient command to update the Windows git binary

git update-git-for-windows

Which is useful since ADO likes to complain about old git clients —

remote: Storing packfile... done (51 ms)
remote: Storing index... done (46 ms)
remote: We noticed you're using an older version of Git. For the best experience, upgrade to a newer version.

Python Selenium Headed v/s Headless

We are automating a file download — it works fine when running headed, but headless execution doesn’t manage to log in. Proxying the requests through Fiddler show that several JavaScript pages download unexpected content.

I’ve added a user-agent to the request, but I’ve noticed that the ChromeDriver also sets sec-ch-* headers … I expect the null sec-ch-ua causes the web server to refuse our request. I don’t see any issues in the ChromeDriver repo for the sec-ch-* headers … and I don’t really want to walk back versions until I find one that doesn’t try setting this header value. Firefox’s GeckoDriver, though, doesn’t set them … so I moved the script over to use Firefox instead of Chrome and am able to download the file.

Headed run:

GET /o/telx-theme/css/A.bootstrap.css+slick,,_slick.css,Mcc.JKqfH-juDS.css.pagespeed.cf.ZO22sEGAvO.css HTTP/1.1
Host: example.com
Connection: keep-alive
sec-ch-ua: “Chromium”;v=”92″, ” Not A;Brand”;v=”99″, “Google Chrome”;v=”92″
sec-ch-ua-mobile: ?0
User-Agent: “Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36
Accept: text/css,*/*;q=0.1
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: no-cors
Sec-Fetch-Dest: style
Referer: https://example.com/web/guest/login
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Cookie: JSESSIONID=0330C2C988F31010790779A126EA6F55.node1; COOKIE_SUPPORT=true; GUEST_LANGUAGE_ID=en_US; AWSELB=039B496118DDEAD697B2B51C93852940763289C324F9E7C7223F953330AF5506573D13C4D5599541FD3CADB645303C1CAEB6D26992826965DA6C8BEDBDE9C297AE26CD76ED; AWSELBCORS=039B496118DDEAD697B2B51C93852940763289C324F9E7C7223F953330AF5506573D13C4D5599541FD3CADB645303C1CAEB6D26992826965DA6C8BEDBDE9C297AE26CD76ED; TS0194d418=01092b79076749232d762d2a6c232e015d103453fbeda3826bd3d20e1d937f5a90cabe03655c97a79198969eea539e4c2e7fc426216092c78ccda85763d52300ce05672704e45b4fc25516d2c24279656db7b0242f7c8b9c8bfed35b7608afb0c54bbc33d489f431059d048094c1e707a20d28031885ca6c61f81613ac299044f0c2b9ba36

 

Headless run:

GET /o/telx-theme/css/A.bootstrap.css+slick,,_slick.css,Mcc.JKqfH-juDS.css.pagespeed.cf.ZO22sEGAvO.css HTTP/1.1
Host: example.com
Connection: keep-alive
sec-ch-ua:
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36
Accept: text/css,*/*;q=0.1
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: no-cors
Sec-Fetch-Dest: style
Referer: https://example.com/web/guest/login
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US
Cookie: JSESSIONID=F4293ECE33B134CC368C0E62D6923B48.node1; COOKIE_SUPPORT=true; GUEST_LANGUAGE_ID=en_US; AWSELB=039B496118DDEAD697B2B51C93852940763289C324A5AB24AE470C70960B5319A93C181302D27B4C9425A4AA05795334C4404D491FBCC8E6A9B809746A802EAC2EC8C2FBFA; AWSELBCORS=039B496118DDEAD697B2B51C93852940763289C324A5AB24AE470C70960B5319A93C181302D27B4C9425A4AA05795334C4404D491FBCC8E6A9B809746A802EAC2EC8C2FBFA; TS0194d418=01ba3b12a4ef612e3839114024b5082fd19d56b17293c914ff867740ad37ae362e385934695ad3fc275074bfd1ee24c7d1591b146ad39d153a8758aecc8eb44d374dc1c689e540deca9566f723df65e9f5ad26551e25bacd5df14e4e6104a91a0ecdb59a65176bd5a0ebed284847e0e6618a05ed1d9db6b544e195d8e1f41164e7199a6596

Fortify on Demand Remediation: Command Injection

Any time user input is used to shell out and execute a command, you risk the user executing more than you want. I can string together commands in DOS using &, in Unix using ; … and stringing together commands and then executing them can blow things up spectacularly.

You can add any sort of filter to the user input to sort this … however, it doesn’t absolutely mean the vulnerability doesn’t exist. If your “user” input is trusted (in this case, it’s an automated process where some code calls some other code … so “passing” is good enough), no big. But if there are actual users involved, you should also filter out any characters that are used to string commands together.

Fortify on Demand Remediation — Cross-Site Scripting DOM (JS)

This vulnerability occurs when you accept user input or gather input from a AJAX call to another web site and then use that input in output. The solution is to sanitize the input, but Fortify on Demand seems to object strenuously to setting innerHTML … so filtering alone may not be sufficient depending on how you subsequently use the data.

To sanitize a string in JavaScript, use a function like this:

/**
 * Sanitize and encode all HTML in a string
 * @param  {string} str  The input string
 * @return {string} –    The sanitized string
 */
 var sanitizeHTML = function (str) {
    return str.replace(/&/g‘&amp;’).replace(/</g‘&lt;’).replace(/>/g‘&gt;’);
};

This will replace ampersands and the < and > from potential HTML tags with the HTML-encoded equivalents. To avoid using innerHTML, you might need to get a little creative. In many cases, I have a span where the results are displayed. I color-code the results based on success/failure … in that case, I an replace innerHTML with a combination of setting the css color style element to ‘green’ or ‘red’ then setting the innerText to my message string.

I can bold an entire element using a similar method. Changing some of the text, however … I haven’t come up with anything other than breaking the message into multiple HTML elements. E.g. a span for “msgStart”, one for “msgMiddle”, and one for “msgEnd” – I can then bold “msgMiddle” and set innerText for all three elements.

Fortify on Demand Remediation – Header Manipulation: Cookies

This is a quick one — putting user input into a cookie is bad — they can throw in CRLF’s and add extra “stuff” into the header

setcookie("ECCKTHistoryCookieSamName", $strLogonUserID, time()+86400, "/sampleTool", $cookiescope, 1);

Strip out the CR, LF, and CRLF’s:

setcookie("ECCKTHistoryCookieSamName", str_replace(array("\r\n", "\n", "\r"), ' ', $strLogonUserID), time()+86400, "/sampleTool", $cookiescope, 1);

 

Fortify on Demand Remediation – Password Management: Hardcoded Password and Privacy Violation

These two vulnerabilities occur in the obvious case — you’ve hard coded a password or some sort of private info (e.g. SSN) and then printed it out to the browser. Don’t do that! But it also seems to occur quite frequently when Fortify on Demand doesn’t like your variable name. As an example, I have a string that provides a consistent error message when user authentication fails.

I then print the string to the screen when the user’s logon fails. Fortify says I am disclosing the user’s password. I’m obviously not. Simply renaming the variable sorts it. Now … yes, this is silly. But it’s a lot easier than trying to convince someone in Security to manually review the code, acknowledge that something about a bad password error is a totally reasonable (and descriptive) variable name, and add an exception for your code. Since bad password is error 49, I just used that in the now less descriptive variable name [ (1) Not too many people know the LDAP error codes off the top of their head, and (2) there are actually a handful of ldap bind return codes that will print this error].

Fortify on Demand Remediation – SQL Injection

This vulnerability occurs when you accept user input and then use that input in a SQL query. The basic remediation is to use oci_bind_by_name to bind variables into placeholders.

A query using an equivalence clause

The simplest case is a query with an equivalence clause.

The code:

        $strQuery = "SELECT DISTINCT EXCHANGE_CARRIER_CIRCUIT_ID, CIRCUIT_DESIGN_ID FROM circuit$strDBLink WHERE EXCHANGE_CARRIER_CIRCUIT_ID = '$strECCKT' ORDER BY CIRCUIT_DESIGN_ID";
        $stmt = oci_parse($kpiprd_conn, $strQuery);
        oci_set_prefetch($stmt, 300);
        oci_execute($stmt);

Becomes:

        $strQuery = "SELECT DISTINCT EXCHANGE_CARRIER_CIRCUIT_ID, CIRCUIT_DESIGN_ID FROM circuit$strDBLink WHERE EXCHANGE_CARRIER_CIRCUIT_ID IN :ecckt ORDER BY CIRCUIT_DESIGN_ID";
        $stmt = oci_parse($kpiprd_conn, $strQuery);
        oci_bind_by_name($stmt, ':ecckt', $strECCKT);
        oci_set_prefetch($stmt, 300);
        oci_execute($stmt);

The same placeholder can be used with the like query. Use “select something from table where columnname like :placeholdername” followed by an oci_bind_by_name($stmt, “:placeholdername”, $strPlaceholderVariable).

A query using an IN clause

– is a little tricker. You could iterate through the array of values and build :placeholder1, :placeholder2, …, :placeholdern and then iterate through the array of values again to bind each value to its corresponding placeholder. A cleaner approach is to use an Oracle collection ($coll in this example) and binding the collection to a single placeholder.

            $arrayCircuitNames = array('L101 /T1    /ELYROHU0012/ELYROHXA32C','111  /ST01  /CHMPILCPF01/CHMPILCPHH3','C102 /OC12  /PHLAPAFG-19/PHLAPAFGW22')
            $strQuery = "SELECT CIRCUIT_DESIGN_ID, EXCHANGE_CARRIER_CIRCUIT_ID  FROM circuit$strDBLink  WHERE EXCHANGE_CARRIER_CIRCUIT_ID in (SELECT column_value FROM table(:myIds))";            $stmt = oci_parse($kpiprd_conn, $strQuery);
            $coll = oci_new_collection($kpiprd_conn, 'ODCIVARCHAR2LIST','SYS');
            foreach ($arrayCircuitNames as $key) {
               $coll->append($key);
            }
            oci_bind_by_name($stmt, ':myIds', $coll, -1, OCI_B_NTY);
            oci_set_prefetch($stmt, 300);
            oci_execute($stmt);
Queries with multiple LIKE conditions

Queries with an OR’d group of LIKE clauses can be handled in a similar fashion – either iterate through the array twice or create a collection with strings that include the wildcard characters, then bind that collection to a single placeholder. Create a semi-join using an EXISTS predicate

            $arrayLocs = array('ERIEPAXE%', 'HNCKOHXA%', 'LTRKARXK%');
            $strQuery = "select location_id, clli_code from network_location$strDBLink where exists (select 1 from TABLE(:likelocs) where clli_code like column_value) order by clli_code";
            $stmt = oci_parse($kpiprd_conn, $strQuery);

            $coll = oci_new_collection($kpiprd_conn, 'ODCIVARCHAR2LIST','SYS');
            foreach ($arrayLocs as $strLocation) {
                $coll->append($strLocation);
            }
            oci_bind_by_name($stmt, ':likelocs', $coll, -1, OCI_B_NTY);
            oci_execute($stmt);
A query using DUAL

Queries where values are selected from DUAL – In some of my recursive queries, I need to include the original input in the result set (particularly, this query finds all equipment mounted under a specific equipment ID – I want to include the input equipment ID as well). Having a bunch of ‘select 12345 from dual’ is fine until I need to use placeholders. This is another place where the collection can be leveraged:

     select column_value equipment_id from TABLE(sys.ODCIVARCHAR2LIST('12345CDE', '23456BCD', '34567ABC') );

Adds each of the values to my result set.

Which means I can use a query like “select column_value as equipment_id from TABLE(:myIDs)” and bind the collection to :myIDs.