Gegen SqlInjection ( + Tests )

    • VB.NET

    Es gibt 3 Antworten in diesem Thema. Der letzte Beitrag () ist von ErfinderDesRades.

      Gegen SqlInjection ( + Tests )

      Wir wollen über einen (Tokenizing-geschützten) WebService erlauben, frei formulierte Select-Statements von der SqlServer Datenbank abzurufen - aber keine Schreibzugriffe.
      Dazu wollen wir alle Objektbezeichner der eingehenden Sql-Strings mit []-Quotes modifizieren, bzw. nach bestimmten Regeln auch ablehnen.

      Die Regeln
      • Worte ausserhalb von '' oder [] werden als Db-Objektbezeichner gequotet (mit []), wenn sie nicht in einer WhiteList aufgeführt sind
      • Folgende Zeichen(folgen) ausserhalb von '' oder [], werden abgelehnt: @, --, ;, /*, */
      • Nicht geschlossene '' oder [] werden abgelehnt.
      • Geschachtelte [] werden abgelehnt
      Hier noch die Sql-WhiteList:

      Quellcode

      1. ALL AND ANY AS ASC BEGIN BETWEEN BY CASE CONTAINS CONVERT DEFAULT DESC DISTINCT ELSE EXCEPT EXISTS FOR FROM FULL GROUP HAVING IF IIF IN INNER INTERSECT INTO IS JOIN LEFT LIKE NOT NULL NULLIF ON OR ORDER OUTER OVER PERCENT RIGHT SELECT SOME THEN TOP TRUNCATE TRY_CONVERT TSEQUAL UNION WHEN WHERE WITH

      Also mit den Worten der Whitelist kann man höchst komplexe Statements verfassen, aber kein Update, Insert, Create, Drop, Exec oder sowas.



      Beispiel einer Test-Ausgabe:

      SQL-Abfrage

      1. ########### Akzeptiere: beliebiges Sql-Segment
      2. JOIN HumanResources.[Employee] AS e where e.Name='hanz'
      3. JOIN [HumanResources].[Employee] AS [e] where [e].[Name]='hanz'
      4. ########### Akzeptiere: beliebiger Text ausserhalb von '' oder []
      5. JOIN Huma' -- # ; @ 'nResources.[Employee] [ -- # ; @ ] AS e
      6. JOIN [Huma]' -- # ; @ '[nResources].[Employee] [ -- # ; @ ] AS [e]
      7. ########### Ablehne: '@ -- # ; /* */' ausserhalb von '' oder []
      8. JOIN Human' @ 'Resources.[Employee] AS @e
      9. --'<Null>
      10. ########### Ablehne: '@ -- # ; /* */' ausserhalb von '' oder []
      11. JOIN HumanResources.[Employee] AS --e
      12. --'<Null>
      13. ########### Ablehne: '@ -- # ; /* */' ausserhalb von '' oder []
      14. JOIN HumanResources.[Employee] AS e */
      15. --'<Null>
      Also man sieht, dasser alle Worte quotet ([HumanResources]), ausser sie sind bereits gequotet oder in '' (zB 'hanz')

      Die anhängende Solution dreht sich eiglich um nur eine einzige Methode, die die Umarbeitung/Ablehnung vollzieht:

      VB.NET-Quellcode

      1. ''' <summary>Converts sql to a sql-injection-safe version, or reject it by returning null</summary>
      2. Public Shared Function MakeSafe(sql As String) As String




      Aber ich hab auch eine Frage: Was meint ihr: Kann man das Teil doch noch iwie hintergehen?, also iwelche schreibende Anweisungen reinmogeln?



      Testing

      VB.NET-Quellcode

      1. <TestMethod()> Public Sub NoInjectTest()
      2. Dim outs = New List(Of String)
      3. For Each xel In Xml.Linq.XElement.Parse(File.ReadAllText("..\..\testInput.sql")).Elements
      4. Dim title = xel.Attribute("title").Value
      5. Dim sql = xel.Value
      6. outs.Add("")
      7. outs.Add("########### " & title.Trim)
      8. outs.Add(sql.Trim)
      9. sql = NoInjectTester.NoSqlInject.MakeSafe(sql)
      10. If sql Is Nothing Then sql = "--'<Null>"
      11. If sql = "" Then sql = "<Empty>"
      12. If sql.Trim.Contains(Lf) Then outs.Add("")
      13. outs.Add(sql.Trim)
      14. Next
      15. 'File.WriteAllLines("..\..\testExpectedOut.sql", outs) ' einkommentieren macht die akt outs zum ExpectedOut.
      16. Dim rgx = New Regex("\s")
      17. Assert.AreEqual(rgx.Replace(String.Concat(outs), ""), rgx.Replace(File.ReadAllText("..\..\testExpectedOut.sql"), ""))
      18. End Sub
      Also eine testInput.sql wird als xml eingelesen.
      So kann ich Test-Überschrift und Test-Sql sauber trennen - Beispiel:
      Spoiler anzeigen

      SQL-Abfrage

      1. <testinput>
      2. <sql title = "Akzeptiere: beliebiges Sql-Segment">
      3. JOIN HumanResources.[Employee] AS e
      4. </sql>
      5. <sql title = "Akzeptiere: beliebiger Text ausserhalb von '' oder []">
      6. JOIN Huma' -- # ; @ 'nResources.[Employee] [ -- # ; @ ] AS e
      7. </sql>
      8. <sql title = "Ablehne: '@ -- # ; /* */' ausserhalb von '' oder []">
      9. JOIN Human' @ 'Resources.[Employee] AS @e
      10. </sql>
      11. </testinput>[/spoiler][spoiler]
      Daraus wird der Output gebastelt.
      Wenn der Output mit dem Inhalt von testExpectedOut.sql identisch ist ist alles gut und das Proggi beendet.

      Man kann den Input auch ändern, und das neue Ergebnis als Output definieren - s. auskommentierte zeile#15



      Aber wie gesagt: Wichtig ist mir meine Frage, ob ihr meint, dass das so wasserdicht sein könnte, oder welche Denkfehler ich in meim Konzept hab.
      Dateien

      Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „ErfinderDesRades“ ()

      Hallo @ErfinderDesRades

      Coole Sache. Ich habe ein wenig rumprobiert. Verstehe ich das richtig das die Funktion MakeSafe() ein Nothing zurückgeben sollte wenn ein Update, Delete oder Insert enthalten ist richtig?

      Ich habe eigene Tests geschrieben und lustigerweise bekomme ich hier beim ersten Versuch gleich ein "korrigiertes" SQL zurück.

      Ausgangs-SQL:

      SQL-Abfrage

      1. UPDATE [Workspaces] SET [LastUpdateTimestamp] = '2019-08-12T10:03:12', [WorkspaceLastOnline] = '2019-08-12T10:03:12' WHERE [WorkspaceId] = 1

      Was die Function zurückgibt:

      SQL-Abfrage

      1. [UPDATE] [Workspaces][ [SET] ][LastUpdateTimestamp] = '2019-08-12T10:03:12', [WorkspaceLastOnline] = '2019-08-12T10:03:12' WHERE [WorkspaceId] = [1]


      Weiters ist mir aufgefallen das Kommentare im SQL nicht zugelassen werden. Wäre eine überlegung wert:


      Ich hoffe ich habe es richtig verstanden und richtig angewandt.

      Grüße
      Sascha
      If _work = worktype.hard Then Me.Drink(Coffee)
      Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

      ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

      Ich würde in dem Webservice für die "Select-Query Methode" einen separaten Datenbankbenutzer nehmen, der nur eingeschränkte Leserechte hat, nur um sicher zugehen.

      Nofear23m schrieb:

      Verstehe ich das richtig das die Funktion MakeSafe() ein Nothing zurückgeben sollte wenn ein Update, Delete oder Insert enthalten ist richtig?
      Nein, so hohe Ansprüche habich nicht.
      Update und dergleichen werden einfach dadurch ausgehebelt, dass sie in [] gequotet werden (wie alle Worte, die nicht in meiner Sql-WhiteList enthalten sind)
      Durch die Umformung entsteht natürlich unausführbarer Sql-Quatsch und ein Fehler, aber dann sollen die Leuts eben keine Sql-Injection versuchen.
      Bzw der Service kriegt ein Fehlerhandling - das brauch er ja sowieso.
      Ist eben eine Minimal-Lösung, um sicherzustellen, dass schreibendes Sql gleich welcher Art nicht ausgeführt wird.
      Also eine Art 'proof of concept'. Ich hab nämlich große Mühe, Kollegen davon zu überzeugen, dass es möglich ist, auf diese Weise gegen SqlInjection abzusichern.
      Die lehnen clientseitig formuliertes Sql prinzipiell ab, weswegen leider unglaubliche Mengen an SPs gecodet wurden und werden - die DB gleicht nicht der Titanic, sondern eher dem Gletscher, der den Eisberg gekalbt hat, der sie versenkt hat.

      Nofear23m schrieb:

      Weiters ist mir aufgefallen das Kommentare im SQL nicht zugelassen werden. Wäre eine überlegung wert
      Jo, Comments sind nicht zulässig.
      Aber da hab ich nur an Inline-Comments gedacht, weil mit denen gerne SqlInjection betrieben wird (gugge Must-Know: Sql-Injection)
      Aber die /* */ - Syntax will ich auch nicht zulassen, einfach aus Bequemlichkeit.
      (Das wird nämlich dann komplex im Zusammenhang mit ''. Also zB bei dd ' /* ' */zu erkennen: Es ist ein /* im String, und nicht ein ' im Comment)

      Soll ja auch kein User-Tool für Sql-Experimente sein, sondern eine Sicherung, wenn clientseitig generiertes Sql ausgeführt oder in die Ausführung eingebaut werden soll.

      Jdfs /**/ muss ich auch noch ausschliessen - vielen Dank!



      @slice: Ich hab zwar keine Ahnung vonne DB-Rechteverwaltung, aber klingt wie eine saugute Idee!

      Hmm - doch nicht ganz. Wir haben auch Code, wo Bedingungen als Sql-Where-Abschnitt formuliert an SPs gegeben werden, und die SPs führen dann durchaus Schreibzugriffe aus, wo diese Bedingungen eingebaut sind.
      Ist natürlich ebenso SqlInjection-gefährdet.

      Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „ErfinderDesRades“ ()