ฉันจะสร้างแบบสอบถาม SQL ที่กำหนดพารามิเตอร์ได้อย่างไร ทำไมฉันถึงควร?


94

ฉันได้ยินมาว่า "ทุกคน" กำลังใช้การสืบค้น SQL ที่กำหนดพารามิเตอร์เพื่อป้องกันการโจมตีด้วยการแทรก SQL โดยไม่จำเป็นต้องยกเลิกการป้อนข้อมูลของผู้ใช้ทุกชิ้น

คุณจะทำอย่างไร? คุณได้รับสิ่งนี้โดยอัตโนมัติเมื่อใช้กระบวนงานที่จัดเก็บไว้หรือไม่?

ดังนั้นความเข้าใจของฉันจึงไม่ถูกกำหนดพารามิเตอร์:

cmdText = String.Format("SELECT foo FROM bar WHERE baz = '{0}'", fuz)

สิ่งนี้จะถูกกำหนดพารามิเตอร์หรือไม่

cmdText = String.Format("EXEC foo_from_baz '{0}'", fuz)

หรือฉันต้องทำอะไรให้กว้างขวางกว่านี้เพื่อป้องกันตัวเองจากการฉีด SQL?

With command
    .Parameters.Count = 1
    .Parameters.Item(0).ParameterName = "@baz"
    .Parameters.Item(0).Value = fuz
End With

มีข้อดีอื่น ๆ ในการใช้แบบสอบถามแบบกำหนดพารามิเตอร์นอกเหนือจากข้อพิจารณาด้านความปลอดภัยหรือไม่?

อัปเดต: บทความที่ยอดเยี่ยมนี้เชื่อมโยงกับหนึ่งในคำถามที่อ้างอิงโดย Grotok http://www.sommarskog.se/dynamic_sql.html


ฉันพบว่ามันน่าตกใจที่เห็นได้ชัดว่าคำถามนี้ไม่เคยถูกถามใน Stackoverflow มาก่อน ดีมาก!
Tamas Czinege

3
โอ้มันมี แน่นอนว่าคำแตกต่างกันมาก แต่ก็มี
Joel Coehoorn

10
คุณควรใช้การสืบค้นแบบพารามิเตอร์เพื่อป้องกันไม่ให้Little Bobby Tablesทำลายข้อมูลของคุณ ไม่สามารถต้านทาน :)
zendar

4
มีอะไรแย่เกี่ยวกับบล็อก With?
Lurker Indeed

1
ใครมีคำถาม # สำหรับคำถาม "มีอะไรเลวร้ายเกี่ยวกับบล็อกด้วย" ไหม
Jim Counts

คำตอบ:


77

ตัวอย่าง EXEC ของคุณจะไม่ถูกกำหนดพารามิเตอร์ คุณต้องมีการสืบค้นแบบกำหนดพารามิเตอร์ (ข้อความที่เตรียมไว้ในบางแวดวง) เพื่อป้องกันไม่ให้อินพุตเช่นนี้ก่อให้เกิดความเสียหาย:

'; แถบตาราง DROP; -

ลองใส่ตัวแปร fuz ของคุณ (หรือไม่ถ้าคุณให้ความสำคัญกับโต๊ะบาร์ของคุณ) การสืบค้นที่ละเอียดอ่อนและสร้างความเสียหายก็เป็นไปได้เช่นกัน

นี่คือตัวอย่างวิธีการทำพารามิเตอร์กับ Sql Server:

Public Function GetBarFooByBaz(ByVal Baz As String) As String
    Dim sql As String = "SELECT foo FROM bar WHERE baz= @Baz"

    Using cn As New SqlConnection("Your connection string here"), _
        cmd As New SqlCommand(sql, cn)

        cmd.Parameters.Add("@Baz", SqlDbType.VarChar, 50).Value = Baz
        Return cmd.ExecuteScalar().ToString()
    End Using
End Function

บางครั้งกระบวนงานที่จัดเก็บไว้จะได้รับเครดิตในการป้องกันการแทรก SQL อย่างไรก็ตามส่วนใหญ่แล้วคุณยังคงต้องเรียกใช้โดยใช้พารามิเตอร์การค้นหาไม่เช่นนั้นก็ไม่ได้ช่วยอะไร หากคุณใช้กระบวนงานที่จัดเก็บไว้โดยเฉพาะคุณสามารถปิดการอนุญาตสำหรับ SELECT, UPDATE, ALTER, CREATE, DELETE และอื่น ๆ (เกี่ยวกับทุกอย่างยกเว้น EXEC) สำหรับบัญชีผู้ใช้แอปพลิเคชันและได้รับการป้องกันด้วยวิธีนี้


ช่วยอธิบายcmd.Parameters.Add("@Baz", SqlDbType.VarChar, 50).Value = Bazเพิ่มเติมได้ไหม
Cary Bondoc

1
@CaryBondoc อยากรู้อะไร บรรทัดนั้นสร้างพารามิเตอร์ที่เรียก@Bazว่าเป็นชนิดvarchar(50)ที่กำหนดค่าของBazสตริง
JB King

คุณยังสามารถพูดว่า "command.parameters.addiwthvalue (" @ Baz ", 50)"
Gavin Perkins

2
@GavinPerkins สมมติว่าคุณหมายถึงAddWithValue("@Baz", Baz)คุณสามารถทำได้แต่คุณไม่ควรทำโดยเฉพาะอย่างยิ่งเนื่องจากการแปลงค่าสตริงที่แม็พเป็นค่าเริ่มต้นnvarcharเป็นvarcharประเภทจริงเป็นหนึ่งในสถานที่ที่พบบ่อยที่สุดที่สามารถทริกเกอร์เอฟเฟกต์ที่กล่าวถึงในลิงก์นั้น
Joel Coehoorn

15

สุดท้ายแน่นอนเช่น

หรือฉันต้องทำอะไรให้กว้างขวางกว่านี้ ... ? (ใช่,cmd.Parameters.Add() )

การสืบค้นแบบ Parametrized มีข้อดีหลัก ๆ สองประการ:

  • ความปลอดภัย: เป็นวิธีที่ดีในการหลีกเลี่ยง ช่องโหว่ของSQL Injection
  • ประสิทธิภาพ: หากคุณเรียกใช้แบบสอบถามเดียวกันเป็นประจำโดยใช้พารามิเตอร์ที่แตกต่างกันคิวรีแบบพารามิเตอร์อาจอนุญาตให้ฐานข้อมูลแคชคิวรีของคุณซึ่งเป็นแหล่งที่มาของการเพิ่มประสิทธิภาพที่สำคัญ
  • พิเศษ: คุณไม่ต้องกังวลเกี่ยวกับปัญหาการจัดรูปแบบวันที่และเวลาในรหัสฐานข้อมูลของคุณ ในทำนองเดียวกันหากรหัสของคุณเคยทำงานบนเครื่องที่มีภาษาที่ไม่ใช่ภาษาอังกฤษคุณจะไม่มีปัญหากับจุดทศนิยม / เครื่องหมายจุลภาคทศนิยม

5

คุณต้องการใช้ตัวอย่างสุดท้ายของคุณเนื่องจากนี่เป็นเพียงตัวอย่างเดียวที่เป็นพารามิเตอร์ที่แท้จริง นอกเหนือจากข้อกังวลด้านความปลอดภัย (ซึ่งเป็นที่แพร่หลายมากขึ้นคุณอาจคิดว่า) ที่ดีที่สุดคือให้ ADO.NET จัดการกับพารามิเตอร์เนื่องจากคุณไม่สามารถแน่ใจได้ว่าค่าที่คุณส่งผ่านนั้นต้องการเครื่องหมายคำพูดเดียวหรือไม่โดยไม่ต้องตรวจสอบTypeพารามิเตอร์แต่ละตัว .

[แก้ไข] นี่คือตัวอย่าง:

SqlCommand command = new SqlCommand(
    "select foo from bar where baz = @baz",
    yourSqlConnection
);

SqlParameter parameter = new SqlParameter();
parameter.ParameterName = "@baz";
parameter.Value = "xyz";

command.Parameters.Add(parameter);

3
ด้วยสิ่งนี้: สตริง. Net เป็น Unicode ดังนั้นพารามิเตอร์จะถือว่า NVarChar เป็นค่าเริ่มต้น หากเป็นคอลัมน์ VarChar จริงๆอาจทำให้เกิดปัญหาด้านประสิทธิภาพได้มาก
Joel Coehoorn

2

คนส่วนใหญ่จะทำผ่านไลบรารีภาษาการเขียนโปรแกรมฝั่งเซิร์ฟเวอร์เช่น PDO ของ PHP หรือ Perl DBI

ตัวอย่างเช่นใน PDO:

$dbh=pdo_connect(); //you need a connection function, returns a pdo db connection

$sql='insert into squip values(null,?,?)';

$statement=$dbh->prepare($sql);

$data=array('my user supplied data','more stuff');

$statement->execute($data);

if($statement->rowCount()==1){/*it worked*/}

สิ่งนี้จะดูแลการหลบหนีข้อมูลของคุณสำหรับการแทรกฐานข้อมูล

ข้อดีอย่างหนึ่งคือคุณสามารถใส่เม็ดมีดซ้ำได้หลาย ๆ ครั้งด้วยคำสั่งที่เตรียมไว้ซึ่งจะได้เปรียบด้านความเร็ว

ตัวอย่างเช่นในแบบสอบถามด้านบนฉันสามารถเตรียมคำสั่งหนึ่งครั้งจากนั้นวนซ้ำเพื่อสร้างอาร์เรย์ข้อมูลจากกลุ่มข้อมูลและทำซ้ำ -> ดำเนินการหลาย ๆ ครั้งตามต้องการ


1

ข้อความคำสั่งของคุณต้องเป็นดังนี้:

cmdText = "SELECT foo FROM bar WHERE baz = ?"

cmdText = "EXEC foo_from_baz ?"

จากนั้นเพิ่มค่าพารามิเตอร์ วิธีนี้ช่วยให้มั่นใจได้ว่าค่า con จะถูกใช้เป็นค่าเท่านั้นในขณะที่วิธีอื่นถ้าตัวแปร fuz ถูกตั้งค่าเป็น

"x'; delete from foo where 'a' = 'a"

คุณเห็นไหมว่าจะเกิดอะไรขึ้น


0

นี่คือคลาสสั้น ๆ สำหรับเริ่มต้นด้วย SQL และคุณสามารถสร้างจากที่นั่นและเพิ่มลงในคลาสได้

MySQL

Public Class mysql

    'Connection string for mysql
    Public SQLSource As String = "Server=123.456.789.123;userid=someuser;password=somesecurepassword;database=somedefaultdatabase;"

    'database connection classes

    Private DBcon As New MySqlConnection
    Private SQLcmd As MySqlCommand
    Public DBDA As New MySqlDataAdapter
    Public DBDT As New DataTable
    Public BindSource As New BindingSource
    ' parameters
    Public Params As New List(Of MySqlParameter)

    ' some stats
    Public RecordCount As Integer
    Public Exception As String

    Function ExecScalar(SQLQuery As String) As Long
        Dim theID As Long
        DBcon.ConnectionString = SQLSource
        Try
            DBcon.Open()
            SQLcmd = New MySqlCommand(SQLQuery, DBcon)
            'loads params into the query
            Params.ForEach(Sub(p) SQLcmd.Parameters.AddWithValue(p.ParameterName, p.Value))

            'or like this is also good
            'For Each p As MySqlParameter In Params
            ' SQLcmd.Parameters.AddWithValue(p.ParameterName, p.Value)
            ' Next
            ' clears params
            Params.Clear()
            'return the Id of the last insert or result of other query
            theID = Convert.ToInt32(SQLcmd.ExecuteScalar())
            DBcon.Close()

        Catch ex As MySqlException
            Exception = ex.Message
            theID = -1
        Finally
            DBcon.Dispose()
        End Try
        ExecScalar = theID
    End Function

    Sub ExecQuery(SQLQuery As String)

        DBcon.ConnectionString = SQLSource
        Try
            DBcon.Open()
            SQLcmd = New MySqlCommand(SQLQuery, DBcon)
            'loads params into the query
            Params.ForEach(Sub(p) SQLcmd.Parameters.AddWithValue(p.ParameterName, p.Value))

            'or like this is also good
            'For Each p As MySqlParameter In Params
            ' SQLcmd.Parameters.AddWithValue(p.ParameterName, p.Value)
            ' Next
            ' clears params

            Params.Clear()
            DBDA.SelectCommand = SQLcmd
            DBDA.Update(DBDT)
            DBDA.Fill(DBDT)
            BindSource.DataSource = DBDT  ' DBDT will contain your database table with your records
            DBcon.Close()
        Catch ex As MySqlException
            Exception = ex.Message
        Finally
            DBcon.Dispose()
        End Try
    End Sub
    ' add parameters to the list
    Public Sub AddParam(Name As String, Value As Object)
        Dim NewParam As New MySqlParameter(Name, Value)
        Params.Add(NewParam)
    End Sub
End Class

MS SQL / Express

Public Class MSSQLDB
    ' CREATE YOUR DB CONNECTION
    'Change the datasource
    Public SQLSource As String = "Data Source=someserver\sqlexpress;Integrated Security=True"
    Private DBCon As New SqlConnection(SQLSource)

    ' PREPARE DB COMMAND
    Private DBCmd As SqlCommand

    ' DB DATA
    Public DBDA As SqlDataAdapter
    Public DBDT As DataTable

    ' QUERY PARAMETERS
    Public Params As New List(Of SqlParameter)

    ' QUERY STATISTICS
    Public RecordCount As Integer
    Public Exception As String

    Public Sub ExecQuery(Query As String, Optional ByVal RunScalar As Boolean = False, Optional ByRef NewID As Long = -1)
        ' RESET QUERY STATS
        RecordCount = 0
        Exception = ""
        Dim RunScalar As Boolean = False

        Try
            ' OPEN A CONNECTION
            DBCon.Open()

            ' CREATE DB COMMAND
            DBCmd = New SqlCommand(Query, DBCon)

            ' LOAD PARAMS INTO DB COMMAND
            Params.ForEach(Sub(p) DBCmd.Parameters.Add(p))

            ' CLEAR PARAMS LIST
            Params.Clear()

            ' EXECUTE COMMAND & FILL DATATABLE
            If RunScalar = True Then
                NewID = DBCmd.ExecuteScalar()
            End If
            DBDT = New DataTable
            DBDA = New SqlDataAdapter(DBCmd)
            RecordCount = DBDA.Fill(DBDT)
        Catch ex As Exception
            Exception = ex.Message
        End Try


        ' CLOSE YOUR CONNECTION
        If DBCon.State = ConnectionState.Open Then DBCon.Close()
    End Sub

    ' INCLUDE QUERY & COMMAND PARAMETERS
    Public Sub AddParam(Name As String, Value As Object)
        Dim NewParam As New SqlParameter(Name, Value)
        Params.Add(NewParam)
    End Sub
End Class
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.