自分がコーディングしたプログラムを自らコードレビュー（VB.NET）

# レビュー対象のサンプルコード
以下のコードは、SQL Serverへ接続し、SQLを実行した結果をアプリケーション上へ取得する処理を記述しています。ユーザーIDとパスワードを条件として得られた結果を元に、認証してよいユーザーかどうかを処理するためのメソッドになります。

```VB
Imports System.Data.SqlClient

Public Class clsSqlServerRespoder

    Public Function getAutenticate(ByRef systemErrorFlag As Boolean, ByRef userID As String, ByRef password As String, ByRef isAuthenticated As Boolean) As Boolean
        Dim cn As New SqlClient.SqlConnection

        Try
            '環境変数から必要なデータを取得してくる
            Dim devDataSource As String = System.Environment.GetEnvironmentVariable("DEV_DATA_SOURCE")
            Dim devInitialCatalog As String = System.Environment.GetEnvironmentVariable("DEV_INITIAL_CATALOG")
            Dim devUserID As String = System.Environment.GetEnvironmentVariable("DEV_USER")
            Dim devPassword As String = System.Environment.GetEnvironmentVariable("DEV_PASSWORD")
            Dim devTimeout As String = System.Environment.GetEnvironmentVariable("DEV_TIMEOUT")

            'SQL Serverへの接続情報を作る
            Dim connectionString As String = ""
            connectionString &= String.Format("Data Source = {0};", devDataSource)
            connectionString &= String.Format("Initial Catalog = {0};", devInitialCatalog)
            connectionString &= String.Format("User ID = {0};", devUserID)
            connectionString &= String.Format("Password = {0};", devPassword)
            connectionString &= String.Format("Connect Timeout = {0};", devTimeout)

            'SQL Serverへ接続する
            cn.ConnectionString = connectionString
            cn.Open()

            'SQLを文字列として作る
            Dim SQL As String = ""
            SQL &= String.Format("SELECT COUNT(*) AS COUNT ")
            SQL &= String.Format("FROM UserInfo  ")
            SQL &= String.Format("WHERE user_id = '{0}' ", userID)
            SQL &= String.Format("AND ")
            SQL &= String.Format("password = '{0}' ", password)

            'SQLを実行する
            Dim cd As New SqlClient.SqlCommand
            cd.CommandText = SQL
            cd.Connection = cn

            'SQLの実行結果を取得する
            Dim dr As SqlClient.SqlDataReader
            dr = cd.ExecuteReader
            Dim count As Integer
            While dr.Read
                count = dr("COUNT")
            End While

            '取得結果を元に、認証結果を判断する。
            If count = 1 Then
                isAuthenticated = True
            Else
                isAuthenticated = False
            End If

        Catch ex As Exception
            systemErrorFlag = True
            MessageBox.Show("エラーが発生しました： " & ex.Message)
        Finally
            cn.Close()
            cn.Dispose()
        End Try

        Return systemErrorFlag
    End Function

End Class
```

# 問題点① このコードではSQLインジェクションが可能となってしまう
SQL文を単にコードに並べただけでは、SQLインジェクションが可能となってしまいます。**（SQLインジェクションとはなんぞや！と言う人は、Google検索して、先に理解しておいた方が良いかも）**
![コード1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3079289/9073a122-3f82-a816-f554-2be6c755f1bd.png)

試しに、ユーザー情報を以下のように準備し、SQLインジェクションを試してみます。
**※本来、パスワードをハッシュ化して管理したり、パスワードのテキストフォームは見えないように配慮しなければなりませんが、この時点では実装していません。**
![データベース.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3079289/9dc4daa5-627a-a2c8-5c91-fc7368b10761.png)
ユーザーIDとパスワードを入力し、ログインすると、認証の成功の有無が分かるようにしています。
![ログイン画面.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3079289/df53045f-9f16-4ddf-b0ac-52fa0cc0a434.png)
ユーザーIDとパスワードが一致すれば、認証に成功というメッセージを出力するようにしています。
![ログイン成功.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3079289/6344b717-2822-5e5b-ff96-2f6afe4d08e0.png)
またパスワードを誤ったものにすると、ログインに失敗します。ここまでは想定している動作が行われていると思います。
![ログイン失敗.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3079289/cd7626f9-a7d8-6146-7455-e716377e5a5c.png)

しかしユーザーIDの後ろに、[' --]を追加すると、ユーザーIDのみで認証が成功してしまいます。
![不正アクセスパターン.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3079289/815b7d62-a2ea-a9d3-ade3-ddc3e9e42304.png)
組み立てられるSQLは以下のようになっています。"--"の部分は、SQL Server上でコメントアウトという扱いになってしまいます。そのため、本来、ユーザーIDとパスワードに一致することを条件に認証成功としていますが、ユーザーIDのみの条件となってしまいます。

**実行されるSQL**
```SQL
SELECT COUNT(*) AS COUNT FROM UserInfo  WHERE user_id = 'U1234567' --' AND password = '' 
```
結果、（どのようなアプリかにもよりますが、）これによって、"U1234567"ユーザーの個人情報が不正に閲覧・入手されるでしょう。
![不正アクセス成功.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3079289/4102b82c-7f6c-1b2e-9a2b-494332d2e8b7.png)

また、でたらめなユーザーIDの後ろに、[' or 1 = 1 --']を追加すると、ユーザーIDとパスワードに関係なく、認証成功となってしまいます。

**実行されるSQL**
```SQL
SELECT COUNT(*) AS COUNT FROM UserInfo  WHERE user_id = 'U7654321' or 1 = 1 --'' AND password = '76543210' 
```
結果、（どのようなアプリかにもよりますが、）これによって、情報が不正に閲覧・入手されるでしょう。
![無題２.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3079289/b63b3304-defe-4dd4-a86e-e6954c44bbf7.png)

他にも、SQL Server上に保存しているデータを削除することも可能になってしまいます。ユーザーIDの後ろに['; DELETE FROM UserInfo; --]を追加すると、ユーザー情報も削除できてしまいます。（今回は、削除の権限を付与していなかったため、ユーザー情報を削除される心配はありませんでした）

**実行されるSQL**
```SQL
SELECT COUNT(*) AS COUNT FROM UserInfo  WHERE user_id = 'U'; DELETE FROM UserInfo; --' AND password = '01234567' 
```

修正した結果は[こちら](https://qiita.com/portfoliokns3/items/6f4d6e4c0432ba80c646)。

# 問題点②　取得したデータの数をカウントして、認証の有無を判断してしまっている
ユーザーIDとパスワードに一致するレコードの数を、SQL Server側でカウントし、それをアプリケーション上でカウント数をもとに条件分岐するというコードになっており、効率が悪くなっています。
![コード1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3079289/9073a122-3f82-a816-f554-2be6c755f1bd.png)
SQLのコード修正が必要となった際、影響を受ける範囲が大きいことが保守的に問題となることもあります。また"count = 1"と書かれており、マジックナンバーとしてコードが書かれているので、別の開発者が見たときに、この"1"はどのような意味を持つのかが分からなくなるでしょう。
![コード2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3079289/53c9686d-7607-7142-6fa1-3700428c7a08.png)

修正した結果は[こちら](https://qiita.com/portfoliokns3/items/c95f9d9a4af5e65802d6)。


# 注意事項
- この記事では、SQL Server、SSMS、visual studioを使用しているので、試してみたい方は事前に準備が必要です。
- 本記事に掲載されているコードは掲載用にサンプルとして掲載しているものです。したがって本記事を参考にし、コーディングを進める場合は作成しているアプリに応じて、各自で判断して、適応してください。
- 関連する記事の一覧は[こちら](https://qiita.com/portfoliokns3/items/7c789b7e1ba40500abbb#%E3%81%93%E3%82%8C%E3%81%8B%E3%82%89%E6%89%B1%E3%81%A3%E3%81%A6%E3%81%84%E3%81%8D%E3%81%9F%E3%81%84%E4%BA%BA%E5%90%91%E3%81%91%E3%81%AE%E8%A8%98%E4%BA%8B)。
※参照先のリンクが切れている場合は、[トップページ](https://qiita.com/portfoliokns3)から確認してください。

# この記事は誰向けの記事か？
- SQLインジェクション対策をコードに実装したい人
- SQLインジェクションを実際に試した場合のイメージをつかみたい人

# 環境
- OS：Windows10
- [SQL Server](https://www.microsoft.com/ja-jp/sql-server/sql-server-downloads)
- [SQL Server Management Studio（SSMS）](https://learn.microsoft.com/ja-jp/sql/ssms/download-sql-server-management-studio-ssms?view=sql-server-ver16):v19.0.2
- [visual studio 2022](https://visualstudio.microsoft.com/ja/vs/)

# 本記事における注意事項
- 本記事は、備忘録としてまとめたものになります。
- 他の方の参考になる可能性も踏まえて、一般公開も行なっております。
- また記載内容はすべて、正しい内容が記載されているとは限りません。
- 誤った内容を見つけた場合は、ご指摘をお願いいたします。