김승현

[Dreamhack : Web] phpMyRedis 본문

Web/Dreamhack : Web

[Dreamhack : Web] phpMyRedis

kshind 2023. 2. 26. 15:06

문제

 

php로 redis를 관리하는 서비스에서 취약점을 찾아서 flag를 획득하라는 문제인 것 같다.

 

풀이

접속하면 이렇게 되어 있다. 일단 save를 체크하고 return 1;을 submit 해보자

이렇게 특정 주소로 저장되었다고 뜨고 command History에 내용이 입력된다. 이번엔 return 2;를 save없이 submit 해보자.

이렇게 return 2;가 입력되고 저장된 경로가 없는 걸 보니 save를 눌러야 서버 내에 저장 커맨드가 저장되고 

입력 없이 하면 한 번 실행되고 끝인 것 같다.

command history 오른쪽 끝에 있는 reset 버튼을 누르면 내용들이 초기화된다. 이번엔 오른쪽 젤 위의 config를 눌러보자.

이렇게 config 선택창과 , KEY, VALUE 입력창이 뜬다. 

config는 GET과 SET이 있다. 아마 redis의 조회하는 get과 추가하는 set인 것 같다.

이런 식으로 아무 값이나 입력해봤다.

get으로 했을 때 모습

set으로 위와 같이 입력해봤는데 딱히 변하는 건 없었다. php 파일들을 읽어보자.

php 파일에는 config, core, index, reset이 있다. 하나하나 보도록 하자.

 

core.php

<?php 

$REDIS_HOST = 'localhost';
$REDIS_PORT = 6379;

ini_set('session.save_handler', 'redis');
ini_set('session.save_path', "tcp://$REDIS_HOST:$REDIS_PORT");
session_start();

localhost:6379로 열고 redis를 사용하며 세션들을 저장할 경로로 tcp://$REDIS_HOST:$REDIS_PORT를 사용함

 

<index.php>

<?php
    include_once "./core.php";
?>
<html>
    <head></head>
    <link rel="stylesheet" href="/static/bulma.min.css" />
    <body>
        <div class="container card">
            <div class="card-content">
                <div class="columns">
                    <div class="column is-10">
                        <h1 class="title">phpMyRedis</h1>
                    </div>
                    <div>
                        <div class="column is-2"><a href="/config.php" class="card-footer-item">Config</a></div>
                    </div>
                </div>
                <form method="post">
                    <div class="field">
                        <label class="label">Command</label>
                        <div class="control">
                            <textarea class="textarea" name="cmd"><?=isset($_POST['cmd'])?$_POST['cmd']:'return 1;'?></textarea>
                        </div>
                        <label class="checkbox">
                            <input type="checkbox" name="save">Save
                        </label>
                    </div>
                    <div class="control">
                        <input class="button is-success" type="submit" value="submit">
                    </div>
                </form>
                <?php 
                    if(isset($_POST['cmd'])){
                        $redis = new Redis();
                        $redis->connect($REDIS_HOST);
                        $ret = json_encode($redis->eval($_POST['cmd']));
                        echo '<h1 class="subtitle">Result</h1>';
                        echo "<pre>$ret</pre>";
                        if (!array_key_exists('history_cnt', $_SESSION)) {
                            $_SESSION['history_cnt'] = 0;
                        }
                        $_SESSION['history_'.$_SESSION['history_cnt']] = $_POST['cmd'];
                        $_SESSION['history_cnt'] += 1;

                        if(isset($_POST['save'])){
                            $path = './data/'. md5(session_id());
                            $data = '> ' . $_POST['cmd'] . PHP_EOL . str_repeat('-',50) . PHP_EOL . $ret;
                            file_put_contents($path, $data);
                            echo "saved at : <a target='_blank' href='$path'>$path</a>";
                        }
                    }
                ?>
            </div>
        </div>
        <br/>
        <div class="container card">
            <div class="card-content">
                <div class="columns">
                    <div class="column is-10">
                        <h1 class="title">Command History</h1>
                    </div>
                    <div class="column is-2"><a href="/reset.php" class="card-footer-item">Reset</a></div>
                </div>
                <div class="content">
                    <ul>
                    <?php
                        for($i=0; $i<$_SESSION['history_cnt']; $i++){
                            echo "<li>".$_SESSION['history_'.$i]."</li>";
                        }
                    ?>
                    </ul>
                </div>
            </div>
        </div>
    </body>
</html>

 

 

index.php는 우리가 접속하자마자 보이는 화면을 구성하는 파일인데 잘 읽어내려가다 보면

class 라벨이 command인 부분을 찾을 수 있다. 이게 우리가 command를 입력하는 그곳인 것 같다.

밑에 textarea 부분에 name=cmd로 지정한다. isset 함수는 변수에 값이 존재하는지 확인하는 함수라고 한다.

$_POST['cmd'])?$_POST['cmd']:'return 1;' 이 내용이 되게 중요할 것 같다. 입력이 없으면 return 1;이 기본적으로 입력됨.

그리고 밑에는 save 체크 박스랑 submit 버튼의 내용들이다.

command 그 textarea에 값이 있다면 새로운 redis 객체를 생성하고 redis_host에 연결을 한다.

그리고 입력된 내용을 reids에 저장하기 위해 json형식으로 저장하는 것 같다.

 

그 아래는 그냥 array_key가 존재하는지 보고 없다면 history_cnt를 0으로 하고 post['cmd'] 즉 command 입력창으로 인해 값이 입력될 때마다 history_cnt가 1씩 증가하는 코드이다.

만약 save 버튼을 눌러서 값을 전송했다면 ./data/에 session_id를 md5로 인코딩해서 저장하는 것 같다.

위에 있던 save를 체크해서 저장한 경우를 보면,

saved at 다음 ./data/ 다음 임의의 문자열이 있는데 이게 session_id를 md5로 인코딩해서 생성된 경로인 것 같다.

 

<reset.php>

<?php
    include_once "./core.php";
    session_destroy();
    header('Location: /');
?>

command history에 있는 reset 버튼에 해당하는 것 같다. session들을 지운다는 내용인 것 같다.

<config.php>

<?php
    include_once "./core.php";
?>
<html>
    <head></head>
    <link rel="stylesheet" href="/static/bulma.min.css" />
    <body>
        <div class="container card">
            <div class="card-content">
                <div class="columns">
                    <div class="column is-10">
                        <h1 class="title">phpMyRedis</h1>
                    </div>
                    <div>
                        <div class="column is-2"><a href="/" class="card-footer-item">Command</a></div>
                    </div>
                </div>
                <form method="post">
                    <label class="label">Config</label>
                    <div class="field">
                        <div class="control">
                            <div class="select">
                                <select name="option">
                                    <option>GET</option>
                                    <option>SET</option>
                                </select>
                            </div>
                        </div>
                    </div>
                    <div class="field">
                        <label class="label">Key</label>
                        <div class="control">
                            <input class="input" type="text" name="key">
                        </div>
                    </div>
                    <div class="field">
                        <label class="label">Value</label>
                        <div class="control">
                            <input class="input" type="text" name="value">
                        </div>
                    </div>
                    <div class="control">
                        <input class="button is-success" type="submit" value="submit">
                    </div>
                </form>
                <?php 
                    if(isset($_POST['option'])){
                        $redis = new Redis();
                        $redis->connect($REDIS_HOST);
                        if($_POST['option'] == 'GET'){
                            $ret = json_encode($redis->config($_POST['option'], $_POST['key']));
                        }elseif($_POST['option'] == 'SET'){
                            $ret = $redis->config($_POST['option'], $_POST['key'], $_POST['value']);
                        }else{
                            die('error !');
                        }                        
                        echo '<h1 class="subtitle">Result</h1>';
                        echo "<pre>$ret</pre>";
                    }
                ?>
            </div>
        </div>
    </body>
</html>

 

대부분 페이지를 구성하는 내용들이고 중요해 보이는 부분은 여기인 것 같다.

POST['option']이 있으면, 여기서 option은 config 페이지에서 get과 set을 의미한다.

redis객체를 생성하고 redis_gost에 연결을 한다.

 

만약 option이 GET이라면,

ret에 key를 json 형태로 해서 저장하는 것 같다.

만약 option이 SET이라면,

ret에 key와 value에 해당하는 내용을 저장하는 것 같다.

둘 다 아니라면 error를 출력하고 종료

그러고 난 후 result와 ret 변수?를 출력하는 것 같다.

 

모든 php를 돌아봤을 때 제일 중요해 보이는 코드 부분은 

우리가 입력한 값을 redis에 저장하는 이 부분인 것 같다.

dir 경로를 확인하기 위해 get으로 key를 dir로 해서 입력해봤는데 /var/www/html이라는 경로에 있는 걸 알 수 있다.

강의를 보면 웹셸을 업로드하는 방법이 있던데 그걸 따라해 보려고 한다.

메모리 내용이 저장되는 dbfileanme에 redis.php로 파일이름을 설정해줬다.

그리고 이건 필수 과정은 아닌데 save를 1 1로 변경해서 60초마다 자동저장되던 걸 1초마다 자동저장이 되게 했다.

문제에 달린 댓글들을 보니까 lua script를 이용하면 편하대서 찾아봐서 입력해줬다.

이제 이 내용이 서버 파일에 있는 redis.php에 저장이 되고 셸이 실행돼서 우리도 사용 가능 할 것이다.

flag의 위치는 다운한 파일 중 docker file에 /flag라고 적혀있다.

이런식으로 입력하면

DH{.....} 내용으로 flag가 출력된 걸 볼 수 있다.