You think you know programming? You think you know languages? heh... as if 🙄
Come back to me when you can write a program that runs in the 3 deadly P's: Perl, Python, and (P)Javascript
Connect with
nc -q 2 -N 52.8.15.62 8001
Flag is located at
./flag.txt
We're given a script that looks like this:
#!/usr/bin/env bash
set -eo pipefail
wrong() {
printf "EXTREMELY LOUD INCORRECT BUZZER!!!\n"
}
trap wrong ERR
code=$(cat)
printf '%s' "$code" | perl -c
printf '%s' "$code" | python3 -c 'import sys,ast; ast.parse(sys.stdin.read())'
printf '%s' "$code" | node -e "const fs=require('fs'), src=fs.readFileSync(0,'utf8'); require('vm').createScript(src)"
perl_out=$(printf '%s' "$code" | perl -)
py_out=$(printf '%s' "$code" | python3 -)
js_out=$(printf '%s' "$code" | node -)
if [[ "$perl_out" == "$py_out" && "$perl_out" == "$js_out" ]]; then
printf "Your triglot compiles!! Here's your output:\n"
printf '%s\n' "$perl_out"
else
exit 1
fi
It seems like we just need to make a script that runs in Perl, Python, and JS, and prints the flag in each one.
To start, a Python / JS polyglot seems pretty straightforward: we can use the fact that //
is floor division
in Python and a line comment in JS to create areas of our script only executed in Python:
3 // 2; [... code only executed in python]
3 // 2; [... code only executed in python]
With this, we can use Python multi-line strings to "comment out" chunks of code to be only executed in JS:
3 // 2; '''
[... code only executed in JS]
//'''
3 // 2; '''
[... code only executed in JS]
//'''
Thus, our Python / JS polyglot that cats the flag is as simple as
3 // 2; print(open('./flag.txt').read()); '''
const { readFileSync } = require('fs'); console.log(readFileSync('./flag.txt').toString());
//'''
3 // 2; print(open('./flag.txt').read()); '''
const { readFileSync } = require('fs'); console.log(readFileSync('./flag.txt').toString());
//'''
Now for the challenging part: adding Perl. We can try to use Perl multiline comments to embed the above Python /
JS payload in a surrounding Perl script, but the syntax is too strange: the =hello
"start comment" deliminator
=hello
... perl comment
=cut
would give syntax errors in JS and Python, and trying to disguise it as a variable assignment e.g.
hello = 5;
a
=hello
... perl comment
=cut
would also lead to it being parsed as an assignment in Perl (with many errors following).
Instead, we can use the convenient Perl __END__
special token
to cause the Perl interpreter to ignore all subsequent text in the payload, avoiding having to manage Perl syntax errors
in the process!
Thus, the main idea becomes doing something like:
open(FH, "<", "./flag.txt"); print <FH>;
__END__=5;
3 // 2; print(open('./flag.txt').read()); '''
const { readFileSync } = require('fs'); console.log(readFileSync('./flag.txt').toString());
//'''
open(FH, "<", "./flag.txt"); print <FH>;
__END__=5;
3 // 2; print(open('./flag.txt').read()); '''
const { readFileSync } = require('fs'); console.log(readFileSync('./flag.txt').toString());
//'''
open(FH, "<", "./flag.txt"); print <FH>;
__END__=5;
3 // 2; print(open('./flag.txt').read()); '''
const { readFileSync } = require('fs'); console.log(readFileSync('./flag.txt').toString());
//'''
where we paste our JS / Python polyglot after the __END__
token to avoid having to deal with Perl syntax there.
(note: we need to do __END__=5
and wrap the __END__
token in an assignment to avoid a JS ReferenceError
or Python
NameError
.)
Finally, we just need to figure out a way to get the first
open(FH, "<", "./flag.txt"); print <FH>;
Perl snippet to not raise syntax errors in Python or JS.
This part is a bit tricky: we need to use the //
trick from before (which is luckily valid Perl for short-circuited "definedness
OR") to comment out Python- / Perl-only areas.
Since single-line comments are started by #
in both Perl and Python, we need to use multiline strings for Python
comments instead. Note that if we're inside a string, #
won't start a single-line comment; thus, we can try to layer strings
such that we are inside a string in Python and not Perl, and vice versa to comment out different sections of code in each.
But there's one last trick, since simply having a python triple-quoted string in Perl will raise syntax errors: Perl "quote-like operators" (which I learned about from this C, Ruby Perl, Python polyglot on GitHub).
We can use the token q="""
to start a multi-line string in Python and a q-string in Perl, where:
"""
ends the Python string, and=
ends the Perl string,
letting us construct something like
q="""
[... this is commented out in python and perl]
=;
[... this is commented out in python only]
#"""
q="""
[... this is commented out in python and perl]
=;
[... this is commented out in python only]
#"""
Weaving these all together, we can construct
3 // 2; q="""
/*=;
open(FH, "<", "./flag.txt"); print <FH>;
#*/
3 // 2;#"""
3 // 2; q="""
/*=;
open(FH, "<", "./flag.txt"); print <FH>;
#*/
3 // 2;#"""
3 // 2; q="""
/*=;
open(FH, "<", "./flag.txt"); print <FH>;
#*/
3 // 2;#"""
for a final payload of
3 // 2; q="""
/*=;
open(FH, "<", "./flag.txt"); print <FH>;
#*/
3 // 2;#"""
__END__=5;
3 // 2; print(open('./flag.txt').read()); '''
const { readFileSync } = require('fs'); console.log(readFileSync('./flag.txt').toString());
//'''
3 // 2; q="""
/*=;
open(FH, "<", "./flag.txt"); print <FH>;
#*/
3 // 2;#"""
__END__=5;
3 // 2; print(open('./flag.txt').read()); '''
const { readFileSync } = require('fs'); console.log(readFileSync('./flag.txt').toString());
//'''
3 // 2; q="""
/*=;
open(FH, "<", "./flag.txt"); print <FH>;
#*/
3 // 2;#"""
__END__=5;
3 // 2; print(open('./flag.txt').read()); '''
const { readFileSync } = require('fs'); console.log(readFileSync('./flag.txt').toString());
//'''
Submitting this, we get our flag:
kevin@ky28059:/mnt/c/users/kevin/Downloads$ cat payload | nc -q 4 -N 52.8.15.62 8001
Your triglot compiles!! Here's your output:
sdctf{y0u_know_s0_m4ny_langu4g3s!}
As I was making this writeup, I realized that the quote-wrapping tricks used for the initial Perl part of the payload was sufficient for the entire payload, as it had code areas that ran exclusively in Perl, JS, and Python.
Then, our entire payload can just be simplified to
3 // 2; q="""
console.log(require('fs').readFileSync('./flag.txt').toString()); /*=;
open(FH, "<", "./flag.txt"); print <FH>;
#*/
3 // 2;#"""; print(open('./flag.txt').read())
3 // 2; q="""
console.log(require('fs').readFileSync('./flag.txt').toString()); /*=;
open(FH, "<", "./flag.txt"); print <FH>;
#*/
3 // 2;#"""; print(open('./flag.txt').read())
3 // 2; q="""
console.log(require('fs').readFileSync('./flag.txt').toString()); /*=;
open(FH, "<", "./flag.txt"); print <FH>;
#*/
3 // 2;#"""; print(open('./flag.txt').read())
(we need to be careful about avoiding =
s in the JS section, lest we end the Perl string prematurely.)