SQL インジェクションの脆弱性を利用してユーザーのパスワードを取得する方法
序文
1. デザインアイデア
SQL インジェクションを実行したい場合は、単純なデータ対話ページが必要となるため、PHP を使用して、ログイン、登録、ホームページの 3 つの部分からなる単純な Web ページを作成します。
ログインするには、アカウントのパスワードを入力し、送信内容がシステムに入力されるまで待つ必要があります。
登録するには、名前、パスワード、携帯電話番号、写真を入力し、送信を待った後にシステムに入る必要があります。
ホームページでは、PHP とデータベースを連携した後、クエリ ステートメントを使用して、簡単な ID 検索ページを設計する必要があります。
シンプルなデータ対話 Web サイトが構築された後、ブラウザーによって送信されたパラメーターの抜け穴を使用して構築パラメーターを変更し、SQL クエリ ステートメントを送信してサーバーに渡し、必要な機密情報を取得します。
2. 設計目的
Webサイト管理者アカウントとログインパスワードを取得する
1. 迅速なウェブサイト構築
最も単純な Web サイトには、ログインと登録の 2 つの機能と、ログイン後のホームページがあり、使用されるテクノロジは HTML+PHP+SQL です。
1. ログインページ
ルートディレクトリの下に新しいshiyanフォルダを作成し、以下に新しいlogin.phpを作成します。まず「アカウント番号とパスワードを入力してください」という大きなタイトルを書き、アカウント番号とパスワードを入力した後、送信ボタンがあります。情報を送信するための PHP ステートメントは、データベースに保存されている情報と一致していることを確認して、システムに入力します。矛盾している場合は、下部にログイン失敗が返されます。
ここではデータベース接続関数を使用する必要がありますが、便宜上、データベース接続を conn.php ファイルとして記述し、login.php 内で直接呼び出します。
<?php
//调用数据库连接文件
include("conn.php");
//接收传递的用户名和密码
$username=$_POST['username'];
$password=$_POST['password'];
//数据库查询语句
//判断输入的账号和密码是否与数据库中内容对应
$uapsql="select user,pass from kkk_tbl where user='$username' and pass='$password';";
//连接数据库
$reslust=mysqli_query($conn2,$uapsql);
// var_dump($reslust);
// var_dump();
//登录成功判断
//加@隐藏报警信息
if(@mysqli_num_rows($reslust)){
//强制跳转游戏页
header('Location:youxi.php');
session_start();
$_SESSION['login']='true';
}else{
$login = "登录失败";
$_SESSION['login']='false';
}
?>
<html>
<head>
<meta charset=utf-8>
</head>
<h1>请输入账号以及密码</h1>
<form action="" method="post" ></br>
<input type="text" name="username"> </br>
<input type="password" name="password"> </br>
<input type="submit">
</form>
//添加注册超链接
<a href="zhuce.php">点击注册</a></br>
//将登录信息显示
<?php echo $login;?>
</html>
2. 登録ページ
新しい zhuce.php を作成します。基本フレームはフォームの下に入力された情報です。入力情報を処理のために PHP に渡し、パラメーター name、password1、password2 などを使用してユーザーが送信した値を受け取ります。
次に、渡された値を判断し、パスワードの長さは 8 桁、パスワードは 2 回同じでなければならない、携帯電話番号は 11 桁、アップロードする写真の種類は jpg 形式であるなどの制限を追加します。
上記の条件にphpを使用して一定の制限を設け、全て満たしていれば登録成功となり、登録情報の一部がフォーム下部に表示されるのでデバッグやトラブルシューティングに便利です。
<head>
<meta charset="utf-8">
</head>
<?php
include("conn.php");
$name=$_POST['name'];
$password1=$_POST['password1'];
$password2=$_POST['password2'];
$shouji=$_POST['shouji'];
$tupian=$_FILES['tupian'];
$tupianname=$_FILES['tupian']['name'];
// echo $name,$password1,$password2,$shouji,$tupian;
if($_SERVER["REQUEST_METHOD"] == "POST"){
if(empty($name) || empty($password1) || empty($password2) || empty($shouji)){
$ERR="账号密码、手机号码不能为空";
//密码长度8位,密码两次输入一致
//密码验证
//手机11位
//文件上传jpg
}elseif(strlen($password1)<8){
$ERR="密码长度不足八位";
}elseif($password1!=$password2){
$ERR="两次输入密码不一致";
}elseif(strlen($shouji)!="11"){
$ERR="手机号码格式有问题";
}else{
// echo $_FILES["tupian"]["name"];
if (file_exists("tupian/" . $_FILES["tupian"]["name"])){
echo $_FILES["tupian"]["name"] . " 文件已经存在。 ";
}else{
// 如果 upload 目录不存在该文件则将文件上传到 upload 目录下
move_uploaded_file($_FILES["tupian"]["tmp_name"], "tupian/" . $_FILES["tupian"]["name"]);
// echo "文件存储在: " . "tupian/" . $_FILES["tupian"]["name"];
}
$sqlinsert="insert into kkk_tbl(user,pass,phone,file)
value('$name','$password1','$shouji','$tupianname');";
var_dump($conn2);
if(mysqli_query($conn2, $sqlinsert)){
$ERR="注册成功</br>";
}else{
$ERR="注册失败</br>";
};
}
}
?>
<form action="" method="post" enctype="multipart/form-data" >
名字:
<input type="text" name="name" > <br>
密码:
<input type="password" name="password1" ><br>
重新输入密码:
<input type="password" name="password2" ><br>
请输入手机号码:
<input type="passwrd" name="shouji" ><br>
上传头像:
<input type="file" name="tupian" ><br>
<input type="submit" value="提交">
</form>
//返回结果
<?php
echo $ERR;
echo "你注册的用户为:".$name."</br>";
echo "你注册的手机号码:".$shouji."</br>";
?>
<img src="<?php echo "tupian/".$_FILES["tupian"]["name"]; ?>"
3. データベース接続ページ
同様に、新しい conn.php ファイルを作成します。サーバーはローカル 127.0.0.1、データベース管理者は root、パスワードは root、データベース名は kkk です。php ステートメントを実行してデータベースを作成します。
<head>
<meta charset=utf-8>
</head>
<?php
$servername = "localhost";
$username = "root";
$password = "root";
$dbname = "kkk";
// 创建连接
$conn = mysqli_connect($servername, $username, $password);
$conn2 = mysqli_connect($servername, $username, $password, $dbname);
// 检测连接
if (!$conn) {
die("连接失败: " . mysqli_connect_error());
}else{
// echo "数据连接成功</br>";
if(mysqli_connect($servername, $username, $password, $dbname)){
// // echo "数据库表已经存在";
// $conn2 = mysqli_connect($servername, $username, $password, $dbname);
}else{
echo "开始自动创建数据库</br>";
$sql = "create DATABASE ".$dbname;
mysqli_query($conn, $sql);
echo "数据库创建成功</br>";
$createtbl="CREATE TABLE IF NOT EXISTS `kkk_tbl`(
`id` INT UNSIGNED AUTO_INCREMENT,
`user` VARCHAR(10) NOT NULL,
`pass` VARCHAR(10) NOT NULL,
`phone` VARCHAR(11) NOT NULL,
`file` VARCHAR(30) ,
PRIMARY KEY ( `id` )
)ENGINE=InnoDB DEFAULT CHARSET=utf8;";
$conn2 = mysqli_connect($servername, $username, $password, $dbname);
mysqli_query($conn2, $createtbl);
echo "数据表创建成功</br>";
}
}
// mysqli_close($conn);
?>
4. ホームページ (ログイン後、ここにジャンプします)
主に SQL クエリを使用してユーザー名とユーザー ID を表示する、シンプルな関数設計で新しい youxi.php ファイルを作成します。
ホームページのデザインの上部に、ログイン後のセッション情報プロンプト (バグのデバッグに便利で、削除可能)、中央のタイトル、およびクエリ ページへのハイパーリンクをエコーします。
なぜセッション情報を設計する必要がないのですか? 主な理由は、このページには論理的な抜け穴があり、侵入者はログインせずにアクセスできるためです。それは本当です。
<?php
include('session.php');
?>
<html>
<head>
<mate charset="utf-8">
<h1>游戏页面</h1>
<a href="select.php">点击查找账号以及ID的对应关系</a>
</head>
</html>
5.セッションページ
ログインページのログイン状態を検出するための session.php を新規作成し、true の場合はログイン成功、false の場合はログインページの次のページにアクセスできません。
<?php
session_start();
echo $_SESSION["login"];
if ($_SESSION["login"] == true) {
echo "您已经成功登陆<a href='zhuxiao.php'>点击注销</a>";
} else {
$_SESSION["login"] == false;
die("您无权访问,<a href='login.php'>点击跳转登录页面</a>");
}
?>
6. ログアウトページ
新しいzhuxiao.php
ログイン後に完全にログアウトしたい場合は、セッションのステータスをログアウトして false に設定する必要があります。破壊した後、強制的にログインページに移動します。
<?php
session_start();
$_SESSION["login"]='false';
session_destroy();
header('Location:login.php');
?>
7. クエリページ
新しく作成された select.php
クエリはデータベースを使用する必要があるため、conn.php データベース接続ファイルが呼び出されます。クエリ関数はホームページの一部であるため、session.php ファイルを呼び出すためのセッション権限を与える必要があります。
データベース テーブルからクエリされた ID とユーザー情報が下部のディスプレイに返され、タイトルの上のプロンプト情報もデバッグを高速化するためのものです。
<?php
include('conn.php');
include('session.php');
$chaxun=$_GET['chaxun'];
if(is_numeric($chaxun)){
$chaxun = "id = $chaxun;";
}else{
$chaxun = "user = '$chaxun';";
}
$chaxun="select * from kkk_tbl where $chaxun;";
echo $chaxun;
// echo $chaxun;
$lianjie=mysqli_query($conn2,$chaxun);
$row=@mysqli_fetch_assoc($lianjie);
$user=$row['user'];
$id=$row['id'];
// var_dump($lianjie);
// while($row=mysqli_fetch_assoc($lianjie)){
// $user=$row['user'];
// $pass=$row['pass'];
// $phone=$row['phone'];
// echo "user:".$user."</br>"."pass:".$pass."</br>"."phone:".$phone."</br>";
// }
?>
<html>
<head>
<mate charset="utf-8">
<h1>请输入你要查询的id或者账号名字</h1>
<form action="#" method="get">
<input type="text" name="chaxun">
<input type="submit" >
</form>
<?php
echo "id:".$id."</br>"."user:".$user."</br>";
?>
</head>
</html>
8. データベース
ここでは mysql5.7 を使用しています。コマンドラインからログインすると、テーブル内の情報をより直感的に確認できます。
2. SQLインジェクションの例(小規模テスト)
SQL 脆弱性挿入の原則を実践するだけですが、実際的な価値はありません。
1. 脆弱性のロジックを推測する
ウェブサイトの php ウェブページを例にとると、ブラウザのソース コードでは、ウェブサイトがデータのやり取りに PHP を使用していることがわかります。
Web ページに入力できる値は ID とユーザーのみであるため、ブラウザーから渡された ID とユーザーが PHP コード内の新しい変数に割り当てられると推測されます。
私たち開発者は Web ページの実装ロジックを知っているため、送信プロセス中に $chaxun の値を変更することで SQL インジェクションを行うことができます。他の馴染みのない Web ページの場合、コード ロジックは引き続き推測を試みる必要があります。
SQL インジェクションの核心は、値渡しインターフェイスを介してデータベース ステートメントを操作して、期待される結果を達成することです。
2. フィールドの数を推測します
Web ページ内の SQL ステートメントの構造は次のようになっていることがわかります。
インジェクションの 1 つのアイデアは、' と --+ または # を使用して無駄なコンテンツをコメントし、クエリしたいステートメントを渡すことです。
SQL インジェクションのポイントを見つけたら、ほとんどのフィールドが order by を使用していると推測し、それらをソートしてフィールド数を取得します。
1回目の試行
テキスト入力ボックスに次のステートメントを入力して、前の値を置き換えます。
admin'order by 1
結果はありませんでした。余分な一重引用符がステートメントの実行に影響を与えることが判明しました。
2回目の試行
SQL ステートメントのいくつかの特性を使用して注釈を付け、影響を排除します。
admin'order by 1 or '1'='1
結果は ID とユーザー名を返していることがわかります。これは、次の内容をコメントアウトしたステートメントでソート操作を実現できることを証明しています。
次に、このブレークスルーを使用して、フィールドの数を決定するために複数の試行を行います。
3回目の試行
admin' order by 2#'
ソートステートメントが有効になります
4回目の試行
admin' order by 3#'
ソートステートメントが有効になります
5回目の試行
admin' order by 4#'
ソートステートメントが有効になります
6回目の試行
admin' order by 5#'
ソートステートメントが有効になります
7回目の試行
admin' order by 6#'
並べ替えステートメントが無効です。テーブルにフィールドが 5 つしかないことを示しています
数回の試行の後、フィールドの数が決定し、次にフィールドの位置と名前が必要になります。
3. フィールド名を推測します
クエリステートメントを使用してエコーの場所を見つけます
1回目の試行
admin'union select 1,2,3,4,5#'
リターンは期待どおりですが、フィールドの位置を決定できません
2回目の試行
admin を null 値に変更して、値を渡した後、ID とユーザーが 12345 の数値で表され、フィールドの位置を決定できるようにします。
-admin'union select 1,2,3,4,5#'
説明、最初のフィールドは id、2 番目のフィールドはユーザー
4. データベース名を推測します
id と user の正確な位置は上記で取得されているため、結果を取得するには、それらを対応する位置にある 2 つの出力関数に置き換えるだけで済みます。
1回目の試行
-admin'union select user(),database(),3,4,5#'
データベース名は kkk で、データベース ユーザーはローカル ルートであると結論付けられます。
私たちの目的は、アカウント番号とパスワードを取得することです。これで、ライブラリ名 kkk、ライブラリのユーザー ルート、ライブラリ内のテーブルには 5 つのフィールドがあり、最初のフィールドは id、2 番目のフィールドはユーザー名であることが分かりました。
しかし、パスワードフィールドはどこにあるのか、その内容は何なのか? わかりません。
そこでデータベーステーブルに注入します
5. データベーステーブルを推測する
テーブル内の対応するパスワード フィールドの内容を推測するには、まずテーブル名を知る必要があります。
ここでは、データベース内の特別なテーブルを使用する必要があります。information_schema.tablesは、データベース内のすべてのテーブルを保存する特別なテーブルです。
select * from information_schema.tables;
データベースのコマンドラインで実行すると、返される結果は次のようになります
しかし、今はデータベース コンソールに入ることができません。クエリ ステートメントの文法の抜け穴を使用して、SQL ステートメントを挿入して必要なものを返すことしかできません。
1回目の試行
kkk データベースの下のテーブルのすべての内容をクエリします。
-admin'union select user(),database(),3,4,5 from information_schema.tables where TABLE_SCHEMA="kkk";#'
返品結果が期待に応えられませんでした
よく見ると、注入ステートメントでは検索対象のテーブル内の特定の情報が指定されておらず、検索条件ではテーブルを正確に見つけることができないことがわかります。
2回目の試行
そこで、最初の user() 関数と 2 番目の database() 関数を 1 に変更し、テーブル名 table_name を変更します。これにより、このステートメントの実行後、サーバーは table_name に従ってテーブル名を返すようになります。
-admin'union select 1,table_name,3,4,5 from information_schema.tables where TABLE_SCHEMA="kkk";#'
ご覧のとおり、テーブル名は kkk_tbl です。
これでテーブル名とテーブルの最初の 2 つのフィールド名が分かりましたが、残りのフィールドを取得するには、残りのフィールド名を推測する必要があります。
3回目の試行
同じメソッドを使用して残りのフィールドをクエリし、2 番目に列名 colum_name、つまりフィールド名を出力させます。
-admin'union select 1,column_name,3,4,5 from information_schema.columns where TABLE_name="kkk_tbl";#'
出力内容はフィールドの最初のIDのみであり、残りの4つのフィールドは表示されないことがわかります。
4回目の試行
したがって、SQL インジェクション ステートメントを最適化し、最初のフィールドの 2 番目の位置にすべての戻り値を文字列の形式で返す必要があります。group_concat() 関数を使用して実現します。
-admin'union select 1,group_concat(column_name),3,4,5 from information_schema.columns where TABLE_name="kkk_tbl";#'
結果には、id、user、pass、phone、file の 5 つのフィールドの名前が出力されます。
これまでのところ、データベース名 kkk、データベース ユーザー root、テーブル名 kkk_tbl、テーブル内のフィールドの数は 5、フィールド名は id、user、pass、phone、file であることがわかっています。
パスワード取得まであと一歩
テーブルの 3 番目のフィールドがパスワードであることがわかったら、同じ方法を使用して次のことを試みます。
5 回目の試行 (パスワードブラスト成功)
group_concat() 関数を使用して、テーブルの下のすべてのユーザーとパスワードの値を出力します
-admin'union select 1,group_concat(user,pass),3,4,5 from kkk_tbl;#'
結果は予想どおりで、2 人のユーザー (admin と 111)、パスワードはそれぞれ 123456 と 12345678 です。
コマンドラインでデータが正しいか確認してみましょう
完全一致