CVE-2023-30334
AsmBB v2.9.1 was discovered to contain multiple cross-site scripting (XSS) vulnerabilities via the MiniMag.asm and bbcode.asm libraries.
“AsmBB is very secure web application, because of the internal design and the reduced dependencies. But it also supports encrypted databases, for even higher security.”
“Download, install and hack”
Yes
Solution
The challenge is to attack the latest version of AsmBB, a web-based message board implemented entirely in x86 assembly. The provided Dockerfile builds the asmbb engine using the source files from the asmbb and freshlib repositories.
# Get source files for asmbbRUN wget https://asm32.info/fossil/asmbb/tarball/4c91cddaec/asmbb-4c91cddaec.tar.gz -O asmbb.tar.gz && \ /bin/bash -c "echo 'b1e621d1ae988b35e836ec9142ccc6ce6cf7c24a090c4d973894770a62fa4ddc asmbb.tar.gz' | sha256sum --check" && \ tar -xf asmbb.tar.gz || true && \ mv asmbb-* asmbb# Get source files for freshlib# AsmBB uses functions from freshlibRUN wget https://fresh.flatassembler.net/fossil/repo/fresh/tarball/6636a57441/Fresh+IDE-6636a57441.tar.gz -O fresh.tar.gz && \ /bin/bash -c "echo '5ba395b0e957536bd66abc572414085aab5f2a527d28214881bbba72ec53e00d fresh.tar.gz' | sha256sum --check" && \ tar -xf fresh.tar.gz && \ mv Fresh* Fresh# Build the asmbb engineRUN lib=/Fresh/freshlib TargetOS=Linux /fasm/fasm -m 200000 /asmbb/source/engine.asm /engine
Gaining XSS
The forum is the main feature of AsmBB, and the default build uses a custom markdown-like parser called MiniMag. Our goal is to achieve a GET-based XSS on the admin user, and subsequently abuse admin features for RCE.
Let's take a look at the AsmBB source. render2.asm contains a "hash table" of commands used by the templating engine, mapped to their routines.
PHashTable tableRenderCmd, tpl_func, \
'special:', RenderTemplate.cmd_special, \
'raw:', RenderTemplate.cmd_raw, \
'include:', RenderTemplate.cmd_include, \
'minimag:', RenderTemplate.cmd_minimag, \ ; HTML, no encoding.
'bbcode:', RenderTemplate.cmd_bbcode, \ ; HTML, no encoding.
'html:', RenderTemplate.cmd_html, \ ; HTML, disables the encoding.
'attachments:', RenderTemplate.cmd_attachments, \ ; HTML, no encoding.
'attach_edit:', RenderTemplate.cmd_attachedit, \ ; HTML, no encoding.
'url:', RenderTemplate.cmd_url, \ ; Needs encoding!
'json:', RenderTemplate.cmd_json, \ ; No encoding.
'css:', RenderTemplate.cmd_css, \ ; No output, no encoding.
'equ:', RenderTemplate.cmd_equ, \
'const:', RenderTemplate.cmd_const, \
'enc:', RenderTemplate.cmd_encode, \ ; encode the content in html encoding.
'usr:', RenderTemplate.cmd_user_encode \ ; encodes the unicode content of the user nickname for unicode-clones distinction.
We can see this in action in post_view.tpl where the post is rendered. Depending on format, the post content is either parsed with minimag or bbcode, and the final output is rendered as HTML.
Although the client-side UI only allows us to write content in the MiniMag format, the POST request to submit the post does include a format parameter.
POST /!post HTTP/1.1Host:localhost:9032Content-Length:917...Connection:close...------WebKitFormBoundarydKCsA6RKHAepAWPnContent-Disposition:form-data; name="format"0------WebKitFormBoundarydKCsA6RKHAepAWPnContent-Disposition:form-data; name="source"[http://example.com][My link] ------WebKitFormBoundarydKCsA6RKHAepAWPn--
When set to 1, the format parameter allows us to use the BBCode parser instead. This uses the bbcode command, which calls the .cmd_bbcode routine.
.cmd_bbcode:
; here esi points to ":" of the "bbcode:" command. edi points to the start "[" and ecx points to the end "]"
locals
BenchVar .bbcode_time
endl
BenchmarkStart .bbcode_time
stdcall TextMoveGap, edx, ecx
stdcall TextSetGapSize, edx, 4
mov dword [edx+ecx], 0
add [edx+TText.GapBegin], 4
inc [edx+TText.GapEnd] ; delete the end "]"
stdcall TextMoveGap, edx, edi
add [edx+TText.GapEnd], 8
stdcall TranslateBBCode, edx, edi, SanitizeURL
...
Since the BBCode parser was a newer parser introduced after MiniMag, and isn't enabled by default, we thought this would be the best place to start looking for parser vulnerabilities.
The TranslateBBCode routine from bbcode.asm (found in FreshLib) is then used to parse the BBCode content. Here we see a table of supported BBCode tags.
BBCode is an old markup language that has a rather simple syntax. Tags are enclosed by square brackets, and some tags can have attributes, such as the following URL tag:
[url=https://example.com]My link[/url]
The main loop of the parser is found at .loop. For each character, the logic goes:
if the end of the text has been reached, exit the loop
if it is a newline or space character, skip it
if it is [, process the tag at .start_tag
if it is the start of an emoji, process the emoji
.loop:
mov ecx, [edx+TText.GapEnd]
cmp ebx, [edx+TText.GapBegin]
cmovb ecx, [edx+TText.GapBegin]
sub ecx, [edx+TText.GapBegin]
add ecx, ebx
cmp ecx, [edx+TText.Length]
jae .end_of_text
movzx eax, byte [edx+ecx]
test al, al
jz .end_of_text
cmp al, $0d
je .new_line
cmp al, $0a
je .new_line
cmp al, $20
jbe .next ; skip all whitespace
...
.paragraph_ok:
cmp al, "["
je .start_tag
; here check for emoticons
cmp al, $f0 ; emoji?
jb .continue
...
Otherwise, we go to .continue, where the character is HTML encoded.
.continue:
; html encoding from here
test al, al ; all values > 127 are unicode and should not be encoded.
js .next
movzx eax, byte [tbl_html+eax]
test al, al
jz .del_char
jns .next ; the same as above
lea esi, [eax+tbl_html] ; the address of the replacement string.
lodsb
movzx ecx, al ; length
; insert the replacement html encoding from esi
stdcall TextMoveGap, edx, ebx
stdcall TextSetGapSize, edx, ecx
inc [edx+TText.GapEnd] ; delete the previous char.
mov edi, [edx+TText.GapBegin]
add edi, edx
add [edx+TText.GapBegin], ecx
add ebx, ecx
rep movsb
jmp .loop
Notice that unless the current character is part of an emoji or part of an opening/closing tag, we will reach the HTML-encoding logic. This is done through a simple text substitution that sanitizes angle brackets, quotes, and ampersands.
Since everything outside the opening/closing tag are HTML-encoded, let's take a closer look at the tag-processing logic. When a tag is matched, a string substitution is performed based on the table below.
The 2nd, 3rd, and 4th columns correspond to the start of the tag, end of the attribute, and end of the tag respectively. For instance, the following markup
The attribute value and the content in between the opening/closing tags are processed separately from the tag itself, and are thus subject to HTML-encoding. If there's any parsing bug to be found, it would probably have to be while parsing the tag.
What if we just don't close the tag?
Since the tag isn't being encoded while it is processed, there might be an edge case where the unencoded content is reflected in the absence of a closing ].
We also found two POST-based XSS vectors, which unfortunately were unusable in this challenge in the absence of an open redirect (since the admin bot is only able to visit the challenge page, and no other page).
The first was a POST request to !post. This would have reflected the XSS payload in the page <title>.
The second is a HTTP response splitting attack. The !skincookie endpoint reflects form data in the Set-Cookie header, and allows for for CRLF injection. In addition to XSS, this can be used to set arbitrary cookies and response headers.
Gaining RCE
Armed with admin privileges, one would see a suspiciously named setting in /!settings.
A setting called "Pipe the emails through" sure sounds promising for RCE. Looking for the form key smtp_exec shows us that this option is being used in commands.asm when sending a user activation email.
Looks like our smtp_exec option is being passed to Exec2. A quick look at process.asm reveals that this spawns a child process with our input. Great!
body Exec2
.pArgs dd ?
begin
pushad
stdcall StrSplitArg, [.hCommand]
mov [.pArgs], eax
mov eax, sys_fork
int $80
test eax, eax
jnz .parent ; this is the parent process
; here is the child.
DebugMsg "Child process here!"
...
All we have to do now is to change this option to a payload that sends us the flag.
Here's the final exploit that we will serve to the admin. Here, I used a first-stage payload to keep the exploit small, but serving the whole exploit in one payload would work as well.