PwnTheBox Web

blog 276

exec1

刚开始并没有发现,exec1.php附件。而则直接对题目进行了目录扫描。根据页面提示发现是命令执行。

PwnTheBox Web

最终找到了index.php/login/,但是页面的内容跟首页的内容一致,都是UUIDUUID。于是就使用了bp进行抓包分析:

PwnTheBox Web

通过排查发现,也没有可以利用的点。于是回头看了下题目之后,就有了如下的文件......

以下是对文件的分析:

<?php

	// Get input

	// $_REQUEST 默认包含了$_GET,$_POST,$_COOKIE的数组的key和value
	// $_REQUEST 接收get或post以及cookie提交的ip参数的值,并以数组保存
	$target = $_REQUEST[ 'ip' ];  
	$target=trim($target); // 对ip进行首尾去空
	// var_dump($target);
	// Set blacklist
	
	# 定义黑名单
	$substitutions = array(
		'&'  => '',
		';' => '',
		'|  ' => '',
		'-'  => '',
		'$'  => '',
		'('  => '',
		')'  => '',
		'`'  => '',
		'||' => '',
	);

	// str_replace(search,replace,subject) 在subject指定的字符串中查找search中的值,将其替换为replace指定的值。search和replace可以为一个数组

	// array_keys() 将数组中的key取出,并作为新数组的value
	$target = str_replace( array_keys( $substitutions ), $substitutions, $target );
    
	# 判断操作系统
	// php_uname('s') 获取操作系统名称,Windows返回Windows NT
	// stristr() 查找字符串第一次出现的位置,找不到则返回false,找到返回匹配的字符串
	if( stristr( php_uname( 's' ), 'Windows NT' ) ) {
		// Windows 
		$cmd = shell_exec( 'ping  ' . $target );
	}
	else {
		// *nix
		$cmd = shell_exec( 'ping  -c 1 ' . $target );
	}

	// 格式化输出命令执行的结果
	echo  "<pre>{$cmd}</pre>";
	

?>

也就是说我们不能使用&、;、|、||、(、)、、$,否则就会被替换为空,由于这里都是单个字符串,我们无法使用双写绕过。在我的印象中,是可以使用%0a进行绕过。%0a是url编码,解码之后是一个换行符。

类似于在终端中执行了ping -c 127.0.0.1 -c 1,又回车执行了另外一个命令。

PwnTheBox Web

hackergame2019-签到题 

进入到题目之后,发现页面的获取flag按钮,点击不了。

PwnTheBox Web

在按钮处,右键检查,发现`disabled=”disabled“`表示禁用button按钮的点击功能。

PwnTheBox Web

我们将其删除之后,回车即可发现页面的按钮可以点击了

PwnTheBox Web

网页读取器

题目有一个附件为`app.py`,进入到题目时如下页面:

PwnTheBox Web

根据题目页面的提示,我们只能输入example.comwww.example.com,并且flag放在了 http://127.0.0.1/flag中。

接下来我们分析app.py文件内容:

PwnTheBox Web

白名单的域名为example.comwww.example.com,并且协议必须为http://

PwnTheBox Web

这里使用了check_hostname()函数对我们的url值进行了检查,那么我们接下来将注意力放到check_hostname函数上面。

PwnTheBox Web

总结:

我们传入的参数必须以http://开头,且必须在白名单内。

于是我们传入http://127.0.0.1/flag@example.com,但是会提示我们404,因为服务器将我们的flag@example.com 看做为一个文件名称,于是提示404

PwnTheBox Web

那么我们该如何是好呢?其实我们只需要在@example.com前面加一个#号即可,因为#号在url地址中表示锚点的意思。它是HTML的概念,表示定位flag文件中的@example.com处。

于是构造http://127.0.0.1/flag#@example.com

PwnTheBox Web

管理员本地访问

进入到题目页面之后提示请从本地访问,那么后端是如何知道我们不是通过本地访问的呢?答案是从HTTP请求头中

PwnTheBox Web

盲猜是X-Forwarded-For请求头(这个最常用),于是尝试提交

PwnTheBox Web

于是就拿到了flag

PwnTheBox Web

以下为常见的获取客户端ip的请求头属性:

X-Forwarded-For: 127.0.0.1
X-Forwarded: 127.0.0.1
Forwarded-For: 127.0.0.1
X-Forwarded-Host: 127.0.0.1
X-remote-IP: 127.0.0.1
True-Client-IP: 127.0.0.1
X-Client-IP: 127.0.0.1
Client-IP: 127.0.0.1
X-Real-IP: 127.0.0.1
Ali-CDN-Real-IP: 127.0.0.1
Cdn-Src-Ip: 127.0.0.1
CF-Connection-IP: 127.0.0.1
X-Cluster-Client-IP: 127.0.0.1
WL-Proxy-Client-IP: 127.0.0.1
Proxy-Client-IP: 127.0.0.1
Fastly-Client-Ip: 127.0.0.1
True-Client-Ip: 127.0.0.1

下载下载

进入到题目之后,是一个下载页面:

PwnTheBox Web

发现flag的下载链接被注释掉了,但是我们知道flag文件的名称为flag.php,并且通过观察第一个a链接的href可以得知是请求?file=flag.txt下载的flag.txt文件。那么我们尝试在url中构造:

?file=flag.php

发现下载了flag.php文件。还有一种方法是将herf属性的值修改为?file=flag.php

PwnTheBox Web

打开该flag文件内容如下:

发现定义了两个函数,一个很明显是加密函数即encrypt(),一个则是解密函数decrypt()

我们在下方写入echo decrypt($flag,$key);之后访问php文件即可拿到flag值。

PwnTheBox Web

快速计算

进入到题目之后发现,让我们提交结果,但是,前提是必须在3秒之内算出结果。很明显以我的手速是不可能的。

PwnTheBox Web

于是我们就借助Python程序。

安装相关模块

python3 -m pip install beautifulsoup4
python3 -m pip install lxml

导入相关模块

import requests   # 导入http的requests模块
from bs4 import BeautifulSoup  导入BeautifulSoup库

简单的来说requests模块的功能主要是可以让Python程序代替浏览器去请求一个页面。同时拿到响应内容。

BeautifulSoup库则最主要的内容是从网站源码中抓取数据。

创建session对象

由于我们需要从网站中计算出表达式的结果,之后再提交给服务器,中间可能发起了两次get请求。那么会话如果没有保持连接的话,会新建一个新的会话。那么我们算的值,跟新会话的表达式不同,所以我们可以使用requests.session(),用来保持会话连接。

url = "https://1221-90ca2e8b-1794-4058-95fa-16c479afdecb.do-not-trust.hacking.run/"
s = requests.session()

发送请求获取响应源码

res = s.get(url)  # 发送请求
content = res.text   # 将响应的源码作为字符串赋值给content变量

获取要计算的表达式

soup = BeautifulSoup(content,'lxml')  # 使用BeautifulSoup的lxml解析器
pLabel = soup.find_all(name='p') # 查找标签p,返回一个列表,包含<p>内容</p>

# 从列表中截取得到我们的表达式,并将字符串表达式赋值给expression变量
expression = str(pLabel[0]).split('<br/>')[1].split('</p>')[0] 

eval()函数计算表达式

eval() 函数用来执行一个字符串表达式,并返回表达式的值。

result = eval(expression) 

此时就已经得到了结果,接下来我们提交给服务器即可。

发送结果到服务器

data = {
    "result":result,
    "submit":"Submit"
}
send = s.post(url,data=data)
print(send.text)

运行即可结果:

PwnTheBox Web

最终代码:

import requests 
from bs4 import BeautifulSoup

url = "https://1221-90ca2e8b-1794-4058-95fa-16c479afdecb.do-not-trust.hacking.run/"
s = requests.session()

res = s.get(url)
content = res.text

soup = BeautifulSoup(content,'lxml')
pLabel = soup.find_all(name='p')
expression = str(pLabel[0]).split('<br/>')[1].split('</p>')[0]

result = eval(expression)
data = {
    "result":result,
    "submit":"Submit"
}
send = s.post(url,data=data)
print(send.text)

该网站已经被黑

进入到页面之后,发现页面还挺酷的。

PwnTheBox Web

题目说是网站被黑,那么肯定会有webshell页面的,于是这里我们使用dirsearch工具进行扫描。

dirsearch -u "https://1221-95831ad7-661a-4299-b076-f7092c5bcb33.do-not-trust.hacking.run/"
PwnTheBox Web

经过运行之后,得到一个shell.php页面,访问之后,需要密码才可以利用:

PwnTheBox Web

于是我们使用bp进行抓包,加载字典进行尝试:

PwnTheBox Web

进行爆破,通过页面返回的不同长度来看,正确的密码可能是hack,因为登录成功和登录失败返回的数据长度是不一致的。

PwnTheBox Web

尝试登录,登录成功后即可拿到flag

PwnTheBox Web

PwnTheBox

进入到题目,发现了一个介绍:

PwnTheBox Web

由于题目时信息搜集,于是我就去百度搜索PwnTheBox大学,发现别人的`writeup`

于是我马上去右键查看源代码,发现了如下内容:

PwnTheBox Web

在响应头中发现了flag

PwnTheBox Web

百度网盘分享链接

进入到题目之后,又是一个爆破的页面,题目说是4位,盲猜是四位数字,于是偷偷的拿出了bp

PwnTheBox Web

payload Type的值设置为Numbers,之后设置从1000开始到9999结束,步长为1。

之后开始爆破:

PwnTheBox Web

得到密码可能为2764,我们这边尝试之后即可得到了flag文件。

PwnTheBox Web

Get

进入到题目之后,在页面中发现了题目的源码,根据题目的源码我们可以得知,我们需要传入what的值为flag,最终的话,才可以输出flag

PwnTheBox Web

尝试传入?what=flag,即可拿到g

PwnTheBox Web

Post

这里只是请求方式跟上道题不一样,其他的都是一样的。这里使用的post数据包,文件内容不再url地址栏中。而在请求主体中。

PwnTheBox Web

这里使用hackerBar插件进行传入post数据包。bp也是可以的,以及curl命令的-d参数也可以发送post数据包。

PwnTheBox Web

使用FireFox也是可以的,在右键检查——>选择网络——>编辑重发数据包即可。

PwnTheBox Web

睿智题目

进入到题目之后,页面隔几秒之后就会自动刷新,根据页面的问题提示,就是说停到某一处就会得到flag

PwnTheBox Web

右键源代码发现是js控制的,于是我们在浏览器端禁用javascript即可。

PwnTheBox Web

至于禁用的方法,自行百度,或在浏览器设置里面去找。

PwnTheBox Web

我们禁用js之后,右键查看源代码,一致刷新页面,直到出现flag即可。

一道很奇怪的题目

进入到题目之后,发现有很多js的alert()弹窗,于是我直接禁用了javascript

PwnTheBox Web

禁用js之后,右键查看源代码之后,发现了一串类似于HTML实体编码的东西

PwnTheBox Web

之后拿到解码网站去解一下即可:

PwnTheBox Web

或者新建一个.html后缀结尾的文件,使用浏览器打开该HTML文件,也是可以解析的。

奇葩的题目

进入到题目之后,题目告诉我们要查看源代码

PwnTheBox Web

在源代码处看到了被url编码的内容。之后又使用eval(unescape(p1) + unescape('%35%34%61%61%32' + p2));对p1和p2变量的url编码进行拼接,使用使用unescape()函数对url编码进行解码,得到字符串,又使用eval()函数,将字符串的内容作为代码执行。

因此url编码的内容可能就是整个题目的逻辑,于是这里将所有的url编码拿到:

%66%75%6e%63%74%69%6f%6e%20%63%68%65%63%6b%53%75%62%6d%69%74%28%29%7b%76%61%72%20%61%3d%64%6f%63%75%6d%65%6e%74%2e%67%65%74%45%6c%65%6d%65%6e%74%42%79%49%64%28%22%70%61%73%73%77%6f%72%64%22%29%3b%69%66%28%22%75%6e%64%65%66%69%6e%65%64%22%21%3d%74%79%70%65%6f%66%20%61%29%7b%69%66%28%22%36%37%64%37%30%39%62%32%62%35%34%61%61%32%61%61%36%34%38%63%66%36%65%38%37%61%37%31%31%34%66%31%22%3d%3d%61%2e%76%61%6c%75%65%29%72%65%74%75%72%6e%21%30%3b%61%6c%65%72%74%28%22%45%72%72%6f%72%22%29%3b%61%2e%66%6f%63%75%73%28%29%3b%72%65%74%75%72%6e%21%31%7d%7d%64%6f%63%75%6d%65%6e%74%2e%67%65%74%45%6c%65%6d%65%6e%74%42%79%49%64%28%22%6c%65%76%65%6c%51%75%65%73%74%22%29%2e%6f%6e%73%75%62%6d%69%74%3d%63%68%65%63%6b%53%75%62%6d%69%74%3b
PwnTheBox Web

之后通过浏览器访问该HTML文件,即可弹出url解码之后的内容,当然也可以使用别的解码网站进行解码。

经过url解码之后得到:

PwnTheBox Web

这里只要输入框中输入的值为67d709b2b54aa2aa648cf6e87a7114f1就可以拿到flag。

验证码

进入到题目,发现是一个加法运算,要问我结果是多少,我看到这个,此时心里高兴极了。

PwnTheBox Web

结果掰掰手指算出了结果为118,结果表单只能输出一位数???学过HTML的应该都知道,这是由于input标签的maxlength属性控制了表单内容的长度

在表单处右键检查

PwnTheBox Web

我们将这个值改大一点即可

PwnTheBox Web

双击修改为100,之后回车即可,之后将结果输入,点击验证即可

PwnTheBox Web

xss

进入到页面提示,根据页面的源代码,我们可以得知,我们需要通过GET请求传入一个name参数。

PwnTheBox Web

在了解代码之前,我们先了解全局变量$_GET,该变量的结果是一个关联数组,它保存了所有通过GET请求传入的参数和其值。

例如test.php的文件内容如下:

<?php
	var_dump($_GET);
?>

当我们访问test.php?a=1&b=2&c=3,同时为其通过GET传入a、b、c三个参数,由于是通过GET请求传入的,所以都存放在全局变量$_GET中。参数名作为数组的KEY,对应参数的值作为对应KEYVALUE

PwnTheBox Web

array_key_exists()的功能为检查数组里是否有指定的键名或索引,当array_key_exists("name",$_GET)表示从$_GET的关联数组中查找KEY为name的元素,如果查到则返回True,否则返回False

&& (并且)的意思,表示前后都要为True,最后的结果才为True

$_GET['name'] != NULL,言外意思就是参数name的值不能为空。

最终的意思,就是说,必须要传入name参数,且值不为空;由于这里的题目名称为xss,所以我们优先考虑构造xss。

尝试构造:

?name=<script>fasdfasdf</script>
?name=<script>alert(/xss/)</script>

就可以拿到了flag????

PwnTheBox Web

但是经过测试,你需要传入的name不为空值就可以拿flag。

达拉崩吧大冒险

进入都题目之后,我们直接进入料理大市场,点击send即可。

PwnTheBox Web

进入到料理大市场之后,王大妈告诉我们吃鸡可以提高攻击力,而我们的目的是打败大龙,所以要吃鸡,于是我们在0只这里右键检查,因为这里是一个列表,每个鸡两块钱,而我们只有50块钱,所以我们这里右键检查,

PwnTheBox Web

我们在0只鸡处,将value设置为一个大于-1900000000000000000的值,或造成一个数据溢出,之后点击send。

PwnTheBox Web

之后,我们的MoneyAttack值就变的非常大了。

PwnTheBox Web

于是我们去打大龙,获取flag即可。

PwnTheBox Web

atchap

查看题目的简介如下

This is a message to all ATchap employees. Our new communication software is now in a beta mode. To register, just enter you email address, you'll receive shortly the activation code.

这是给所有ATchap员工的信息。我们的新通讯软件现在处于测试模式。要注册,只需输入您的电子邮件地址,您很快就会收到激活码。

进入到题目之后发现如下页面:

PwnTheBox Web

当我们尝试输入我自己邮箱的时候,提示

You're not whitelisted or not part of the company..

你没有被列入白名单或者不是公司的一部分..

通过页面底部的提示,我们可以知道邮箱的格式,即firstname.lastname@almosttchap.fr

PwnTheBox Web

通过题目的描述,我们可以知道,只有员工是有这个邮箱的,那么我们在页面中又看到了员工的信息。

PwnTheBox Web

根据邮箱的格式来看

Maud Erateur 的邮箱为:Maud.Erateur@almosttchap.fr

Guy Liguili 的邮箱为:Guy.Liguili@almosttchap.fr

Samira Bien 的邮箱为:Samira.Bien@almosttchap.fr

那么我们得知这个邮箱之后,我们随便将一个员工的邮箱传入,提示如下

You're not using your official address..

你没有使用你的官方地址

这里的话,我们参考如下链接的atchap解析漏洞

https://medium.com/@fs0c131y/tchap-the-super-not-secure-app-of-the-french-government-84b31517d144

于是构造如下,即可拿到flag

xxx@xxx@Samira.Bien@almosttchap.fr
PwnTheBox Web

但是我们这边传入的时候,提示我们格式错误,这里我们使用bp抓包传即可。

PwnTheBox Web

PHP是世界上最好的语言

进入到题目之后,发现提示如下代码

<?php 
show_source(__FILE__);  // 显示源代码
@include_once 'flag.php'; // 引入flag.php文件
//前端攻城狮跑路了,不过PHP是最好的语言
    $a = $_GET['a']; 
    $b = $_GET['b'];  
    $good = false; // 设置$good变量的初始值为false

    // 使用sha1()对$a和$b的值进行散列加密
    // 之后将我们的加密$a之后的值与加密后的$b值进行全等于比较(即比较值,也比较数据类型)
    // 散列加密的特点: 同样的字符串,加密出来的密文也是一样的,所以我们将a和b的值一致即可
    if (sha1($a)===sha1($b)) {
        $good = true;  // 此时$good的值为True
    }
    else die('bypass'); // 条件不成立执行的代码,条件成立就不执行这条

    // $good的值必须为True且必须初始化$_GET['key'],言外之意就是需要传入参数key
    if ($good && isset($_GET['key'])){
        
        /*
      json_decode()函数可以对json格式进行解码,对于值true, false 和 null 会相应地返回 true, false 和 null。而对于其他字符,则会返回stdClass Object。
       
       json格式:
           {
              "name":"x1ong",
              "age":17,
              "address":"China",
              "tel":"178xxxxxxxx"
          }

       */

        $message = json_decode($_GET['key']);
        // $message->key 会取到主键为key的值 之后又与$key做比较,由于$key未被定义,则返回False。那么json中的主键为key的值需要为0,因为0 == Fale,返回为True
        if ($message->key==$key) {
            echo $flag;
        }    
        else die('还差一点就拿到flag了');
    }

总结:

第一个if判断了$a$b的值经过sha1()加密之后的结果和数据类型是否一致。我们这里将两者的值设置一致即可。

第二个if判断$good的值是否为true并且需要传入key参数

第三个if需要将json的值与$key的结果进行比较,php在处理的时候,如果一个变量未定义则返回false,并且返回Notice提示性错误(不影响程序运行),那么这里的$key没有被定义。而0转为布尔值就为false,这里的使用的为 == 比较,该比较不比较两者的数据类型。由于php是弱类型语言,0和布尔值进行比较的时候,会先将布尔值false转为转为数字0,或将数字0转为false之后0和0进行比较。

构造payload:

/?a=123&b=123&key={"key":0}
PwnTheBox Web

exec2

直接上题目附件

<?php 

// 如果问号前面的表达式返回ture,则执行问号后面的,反则执行:后面的
$ip = isset($_POST['ip'])?$_POST['ip']:die();

# 如果成功匹配则返回true,但是经过!取反,意思就变成,如果成功匹配则走if外面的代码
	/*
	^表示以什么开头
	\d表示匹配数字,{1,3} 表示连续匹配3个1到3之间个数字。
	\.表示匹配点

	修饰符\i 表示忽略大小写
	*/

// 必须输入传入含有ip格式的值
if(!preg_match('/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/i',$ip)){
    die("ip 格式错误!");
}

echo strlen($ip); // 输出ip值的长度

// 传入的值必须大于7且小于21才可以
if(strlen($ip)<7||strlen($ip)>21){
    die("ip 长度错误!");
}

// 判断操作系统
if( stristr( php_uname( 's' ), 'Windows NT' ) ) {
		// Windows
		
	$cmd = shell_exec( 'ping  ' .$ip );
}else {
		// *nix
		$cmd = shell_exec( 'ping  -c 1 ' .$ip );
}

	// Feedback for the end user
echo  "<pre>{$cmd}</pre>";

## 要求,利用命令执行getshell

可以看到只要我们传入的参数,满足了这个正则表达式后面不管你输什么,最终也会有匹配(返回true)

PwnTheBox Web

构造payload:

ip=127.0.0.1 | ls
ip=127.0.0.1 ; ls
PwnTheBox Web

访问pwnthebox.txt即可得到flag

PwnTheBox Web

Twice SQL Injection 

进入到题目之后,直接看到了登录页面和注册页面,那么一般情况下,同时有了登录和注册,基本上考的都是二次注入。

PwnTheBox Web

进入到题目之后,点击去注册,之后到了注册页面,我们先正常注册一个账号去尝试。发现这里好像可以输入简介,但是默认是”十月太懒,没有简介。“

PwnTheBox Web

那么我们在注册的地方,假设存在二次注入,我们尝试使用单引号去闭合

username: xxx' union select database() #
password: Admin123

尝试使用xxx' union select database() #这个用户名登录,之后密码输入Admin123。观察是否可以登录成功,我们这里注册的时候,是可以被注册成功的。

PwnTheBox Web

发现在简介处,执行了我们的SQL查询,并且返回了查询结果,也就是当前数据库名称为ctftraining

那么这里就存在一个二次注入。

继续在注册页面,构造如下用户名:

xxd' union select group_concat(schema_name) from information_schema.schemata #
PwnTheBox Web

尝试登录之后,发现简介这里,dump出来了我们所有数据库名称,那么猜测flag在ctftraining数据库

内,接下来我们查看该数据库下的所有数据表。

在注册处构造用户名payload:


ppp'  union select group_concat(table_name) from information_schema.tables where table_schema=database() #
PwnTheBox Web

之后尝试登录,发现dump出当前数据库下的所有数据表

继续构造payload获取flag表下的所有字段:

a' union select group_concat(column_name) from information_schema.columns #
PwnTheBox Web

发现有一个叫做flag的字段,于是构造语句:

aa' union select flag from flag #
PwnTheBox Web

之后我又在github上面找到了该题目的源码,对源码分析如下:

<?php
error_reporting(0);

session_start();
/**
 * Database mysql
 */
$db_host = "127.0.0.1";
$db_user = $db_pass = "root";
$db_name = "ctftraining";
$conn = mysql_connect($db_host, $db_user, $db_pass) or die('Could not connect: ' . mysql_error());

mysql_select_db($db_name, $conn) or die('Could select database: ' . mysql_error());

register_shutdown_function(function () {
	global $conn;
	mysql_close($conn);
});

function query($sql) {
	$result = mysql_query($sql);
	$rows = mysql_fetch_assoc($result);
	return $rows;
}
/**
 * Main
 */

// 判断action参数是否已经初始化,如果没有初始化则执行:后面的,否则执行?后面的。

$action = isset($_GET['action']) ? $_GET['action'] : "index";  

/*
 判断$action的值是否在['login','reg']数组里面,如果在则为true,否则为false,由于这里使用了!取反,那么意思就变为了,如果$action的值在数组里面,则返回flase,不在则返回true
 并且$_SESSION['username'] 已初始化会返回true,否则返回false,由于这里使用了!表示取反,所以
 这里的意思,就变成了如果$_SESSION['username']没有初始化,则返回false

双双为true之后,会执行if里面的内容
*/
if (!in_array($action, ['login', 'reg']) && !isset($_SESSION['username'])) {
	// 执行跳转,跳转到登录页面
	header("Location: sql.php/?action=login");
	// 退出程序的运行
	exit;
}

// 使用switch()进行匹配
switch ($action) {
// 如果$action的值为login,则执行case里面的,即进行登录页面的认证
case 'login':

	// 判断是否传入用户名
	if (isset($_POST['username'])) {

		// 使用addslashes()函数对单双引号进行转义,比如'变成\'
		$username = addslashes($_POST['username']);

		// 使用md5()函数对接受到的密码进行md5加密返回给$password变量
		$password = md5($_POST['password']);


		// 构造语句库查询
		$res = query("select * from users where username='{$username}' and password='{$password}';");


		// 如果查询的结果不为空,则执行if里面的
		if ($res) {
			// 此时$_SESSION['username']的值为当前登录的用户名
			$_SESSION['username'] = $res['username'];

			// 跳转到主页面,并退出程序
			header("Location: sql.php/?action=index");
			exit;
		}
	}
	?>
	<!-- HTML书写登录框 -->
		<h1>Login</h1>
		<form action="sql.php/?action=login" method="POST">
			Username : <input type="text" name="username">
			<br>
			Password : <input type="password" name="password">
			<br>
			<a href="sql.php/?action=reg">Go to Register</a>
			<input type="submit" value="Login">
		</form>
	<?php

	break;

// 如果$action的值为reg,则执行注册操作的代码
case 'reg':

	// 用户名和密码的值不等于空,则执行if里面的代码,反之执行else里面的。
	if (isset($_POST['username']) && $_POST['username'] != "") {

		// 对用户名的单双引号进行转义
		$username = addslashes($_POST['username']);

		// 对密码进行md5加密
		$password = md5($_POST['password']);

		// 在数据库中插入我们输入的用户名和密码
		if (mysql_query("insert into users(username,password,info) values ('{$username}','{$password}','十月太懒,没有简介');")) {
			header("Location: sql.php/?action=login");
		// 如果mysql_query()查询出错返回注册失败
		} else {
			header("Location: sql.php/?action=reg&msg=注册失败");
		}
		// 退出程序
		exit;
	
	// 如果用户没有输出,则让用户传输注册的值
	} else {
		?>
		<h1>Register</h1>
		<div><?=addslashes($_GET['msg']);?></div>
		<br>
		<form action="sql.php/?action=reg" method="POST">
			Username : <input type="text" name="username">
			<br>
			Password : <input type="password" name="password">
			<br>
			<a href="sql.php/?action=login">Go to Login</a>
			<input type="submit" value="Register">
		</form>
	<?php
}
	break;

// 如果$action的值为change则执行如下代码
case 'change':
	// 判断info值是否被初始化
	if (isset($_POST['info'])) {
		// 对info单双引号的值进行转义
		$info = addslashes($_POST['info']);
		// 在数据库中执行查询
		if (mysql_query("update users set info='{$info}' where username='{$_SESSION['username']}';")) {
			header("Location: sql.php/?action=index");
		} else {
			header("Location: sql.php/?action=change&msg=修改失败");
		}
		exit;
	}
	break;

# 如果$action的值为logout则注销登录
case 'logout':
	session_destroy();
	header("Location: sql.php/?action=login");
	exit;
	break;

// 默认值
case 'index':
default:
	$info = query("select info from users where username='{$_SESSION['username']}';");
	?>
		<h1>Info</h1>
		<div><?=addslashes($info['info']);?></div>
		<hr>
		<div><?=addslashes($_GET['msg']);?></div>
		<br>
		<form action="sql.php/?action=change" method="POST">
			Info : <input type="text" name="info">
			<br>
			<input type="submit" value="Change">
		</form>
		<a href="sql.php/?action=logout">Logout</a>
	<?php
break;
}
echo "<!-- October nb!!!!! -->";

其中我们把注意力放到如下代码:

case 'index':
default:
	$info = query("select info from users where username='{$_SESSION['username']}';");
	?>
		<h1>Info</h1>
		<div><?=addslashes($info['info']);?></div>
		<hr>
		<div><?=addslashes($_GET['msg']);?></div>
		<br>
		<form action="sql.php/?action=change" method="POST">
			Info : <input type="text" name="info">
			<br>
			<input type="submit" value="Change">
		</form>
		<a href="sql.php/?action=logout">Logout</a>
	<?php
break;
}

这里会将我们的存到数据库的用户名拿出来跟username做匹配,之后输出到页面当中。假设我们的用户名为xxx' union select database() #,那么带入到数据库查询的语句就为:

PwnTheBox Web

其中红色字体,为我们的用户名,之后再数据库查询:

PwnTheBox Web

那么这个利用的条件,只需要我们的用户名里面有单引号,即可闭合掉原sql查询语句。

那么我们把目光放到注册页面。

case 'reg':

	// 用户名和密码的值不等于空,则执行if里面的代码,反之执行else里面的。
	if (isset($_POST['username']) && $_POST['username'] != "") {

		// 对用户名的单双引号进行转义
		$username = addslashes($_POST['username']);
		var_dump($username);
		// 对密码进行md5加密
		$password = md5($_POST['password']);

		// 在数据库中插入我们输入的用户名和密码
		if (mysql_query("insert into users(username,password,info) values ('{$username}','{$password}','十月太懒,没有简介');")) {
			header("Location: sql.php/?action=login");
		// 如果mysql_query()查询出错返回注册失败
		} else {
			header("Location: sql.php/?action=reg&msg=注册失败");
		}
		// 退出程序
		exit;
	

可以看到,这边只是将我们的单引号或者双引号使用addslashes()函数进行转义处理。例如如下代码:

<?php

	echo $_POST['cmd'];
	echo "<br/> ";
	$cmd = $_POST['cmd'];
	echo addslashes($cmd);
?>
PwnTheBox Web

那么当我们在注册处里面传入这样的用户名:xxx' union select database() # 经过addslashes()函数处理之后,就得到这样的用户名:xxx\' union select database() #

之后又带入到了数据库的执行

PwnTheBox Web

假设我们密码为Admin123,那么带入到数据库执行的语句就为:

PwnTheBox Web

由于此时,对我们的单引号做了反斜杠处理,所以没能逃出字符串,于是就是一个普通的字符串,插入到数据库的用户名就为:

PwnTheBox Web

经过转义之后,反斜杠就被去掉了,那么我们这边就得到一个单引号,并且这个单引号没有经过反斜杠处理。于是,当我们登录的时候,就可以使用该用户名登录,并且登录成功之后,会将用户对应的info输出到页面。

$info = query("select info from users where username='{$_SESSION['username']}';");
	?>
		<h1>Info</h1>
		<div><?=addslashes($info['info']);?></div>
		<hr>

由于这里对我们的用户名作为必要条件进行筛选,而我们的用户名为:xxx' union select database() #

经过构造之后就得到这样的语句:

PwnTheBox Web

之后就会将查询结果输出到页面:

PwnTheBox Web

黑曜石浏览器

进入到题目之后,一直提示使用黑曜石浏览器访问,尝试将自己的User-Agent修改为HEICORE,但是还是不行。

PwnTheBox Web

于是去Google一下,发现了这个浏览器的官网 https://heicore.tql.moe/index.html,尝试下载,提示需要注册,尝试去注册发现必须使用黑曜石浏览器去注册...

于是就查看了一下黑曜石官网浏览器的源代码

PwnTheBox Web

发现了黑曜石浏览器的user-agent,于是尝试User-Agent

PwnTheBox Web

分享