2013年12月27日 星期五

ASP.NET FileUpload上傳檔案,讀取Byte檢查副檔名,以圖片為例

為什麼會講上傳檔案,檢查檔案的副檔名呢(如:jpg、png等)?
在之前,總會看書或上網看教學學習時,會講到檔案上傳,一般都是說檢查檔案的副檔名如:test.jpg,但在一次資訊安全系列的講座上,講解到駭客運用檔案偽裝的手法來騙過程式的檢查,看似一個檔案上傳的動作,卻也是被有心人士利用的一個漏洞,如果上傳一個偽裝的檔案,雖然不執行檔案可能不會有事,但如果讓其他使用者下載,並執行偽裝的檔案(正常的執行word、圖片瀏覽等),那麼就有可能讓其他使用者的電腦被植入木馬等惡意程式。

因此在此分享另外一種利用Byte來檢查檔案副檔名。

參考資料如下:
參考1.建議請先看此篇討論的主題內容,因為也會使用到此篇的寫法 請教判斷是否為真的圖檔的方法 
參考2.Validate uploaded image content in ASP.NET

前置作業:
1.請先建立一個aspx網頁,網頁上一個FileUpload、兩個button按鈕
2.在專案中建立一個up_file資料夾,存放上傳的檔案

前端HTML
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
   
        <asp:FileUpload ID="FUpload1" runat="server" />
        <asp:Button ID="Button1" runat="server" Text="上傳方法一" />
        <asp:Button ID="Button2" runat="server" Text="上傳方法二" />
    </div>
    </form>
</body>
</html>

後端程式碼
Imports System.IO
Partial Class FileUpload
    Inherits System.Web.UI.Page

    Protected Sub Button1_Click(sender As Object, e As System.EventArgs) Handles Button1.Click
        'http://www.dotnetexpertguide.com/2011/05/validate-uploaded-image-content-in.html
        '法一
        Dim imageHeader As New Dictionary(Of String, Byte())

        imageHeader.Add("JPG", New Byte() {&HFF, &HD8, &HFF}) 'C#寫法0xFF, 0xD8, 0xFF;VB.NET必須將0x改為&H
        imageHeader.Add("JPEG", New Byte() {&HFF, &HD8, &HFF})
        imageHeader.Add("PNG", New Byte() {&H89, &H50, &H4E})
        imageHeader.Add("TIF", New Byte() {&H49, &H49, &H2A})
        imageHeader.Add("TIFF", New Byte() {&H49, &H49, &H2A})
        imageHeader.Add("GIF", New Byte() {&H47, &H49, &H46})
        imageHeader.Add("BMP", New Byte() {&H42, &H4D})
        imageHeader.Add("ICO", New Byte() {&H0, &H0, &H1})
        Dim header() As Byte = Nothing

        If FUpload1.HasFile Then
            Dim fileExt As String = Nothing
            fileExt = FUpload1.FileName.Substring(FUpload1.FileName.LastIndexOf(".") + 1).ToUpper()
            Dim filetype As String = Nothing
            filetype = fileExt
            Dim tmp() As Byte = imageHeader(fileExt)

            ReDim Preserve header(tmp.Length - 1)
            FUpload1.FileContent.Read(header, 0, header.Length)

            If CompareArray(tmp, header) Then
                Response.Write("Valid ." & fileExt & " file.")
                Try

                    Dim myFileName As String
                    myFileName = FUpload1.PostedFile.FileName
                    Dim c As String = System.IO.Path.GetFileName(myFileName) '取得檔案及副檔名

                    Dim Uploadfile As String = Server.MapPath("./up_file/") & c
                    '==========================================
                    Dim myfilecount As Integer = 2
                    '檢查檔名是否有重複
                    While (System.IO.File.Exists(Uploadfile))
                        c = "img_" & myfilecount '學號_數字
                        Uploadfile = Server.MapPath("./up_file/") & c & "." & filetype '重新命名並加上副檔名
                        myfilecount = myfilecount + 1
                    End While
                    '==========================================

                    Dim myImage As System.Drawing.Image = System.Drawing.Image.FromStream(FUpload1.PostedFile.InputStream)
                    myImage.Save(Uploadfile, ParseImageFormat(filetype))
                Catch ex As Exception
                    Response.Write("sorry,Save error.")
                End Try
            Else
                Response.Write("Invalid ." & fileExt & " file.")
            End If
        End If
    End Sub
    '判斷為Byte
    Function CompareArray(ByVal a1() As Byte, ByVal a2() As Byte) As Boolean
        If a1.Length <> a2.Length Then
            Return False
        End If
        For i As Integer = 0 To a1.Length - 1
            If a1(i) <> a2(i) Then
                Return False
            End If
        Next
        Return True
    End Function

    Protected Sub Button2_Click(sender As Object, e As System.EventArgs) Handles Button2.Click
        '法二
        If FUpload1.HasFile Then
            Dim filetype As String = Nothing
            Dim bool_by As Boolean = True
            If Me.FUpload1.PostedFile.ContentLength > 0 Then

                Dim s As Stream = Me.FUpload1.PostedFile.InputStream
                Dim buffer(s.Length - 1) As Byte
                s.Read(buffer, 0, s.Length)

                Dim header As String = Hex(buffer(0)) & Hex(buffer(1))

                Select Case header
                    Case "FFD8"
                        My.Response.Write("JPG")
                        filetype = "jpg"
                    Case "424D"
                        My.Response.Write("BMP")
                        filetype = "bmp"
                    Case "4749"
                        My.Response.Write("GIF")
                        filetype = "gif"
                    Case "8950"
                        My.Response.Write("PNG")
                        filetype = "png"
                    Case Else
                        My.Response.Write("Not Supported!" & header)
                        bool_by = False
                End Select
                If bool_by = True Then

                    Try

                        Dim myFileName As String
                        myFileName = FUpload1.PostedFile.FileName
                        Dim c As String = System.IO.Path.GetFileName(myFileName) '取得檔案及副檔名

                        Dim Uploadfile As String = Server.MapPath("./up_file/") & c
                        '==========================================
                        Dim myfilecount As Integer = 2
                        '檢查檔名是否有重複
                        While (System.IO.File.Exists(Uploadfile))
                            c = "img_" & myfilecount '學號_數字
                            Uploadfile = Server.MapPath("./up_file/") & c & "." & filetype '重新命名並加上副檔名
                            myfilecount = myfilecount + 1
                        End While
                        '==========================================

                        '重新製作圖檔
                        Dim myImage As System.Drawing.Image = System.Drawing.Image.FromStream(FUpload1.PostedFile.InputStream)
                        myImage.Save(Uploadfile, ParseImageFormat(filetype))

                    Catch ex As Exception
                        Response.Write("sorry,Save error.")
                    End Try

                End If
                s.Close()
                s.Dispose() '釋放所有資源要在最後執行,否則即使存檔也無效
            Else
                bool_by = False
            End If

        End If
    End Sub
    Function ParseImageFormat(ByVal type As String) As System.Drawing.Imaging.ImageFormat
        Select Case type.ToLower()
            Case "jpg"
                Return System.Drawing.Imaging.ImageFormat.Jpeg
            Case "jpeg"
                Return System.Drawing.Imaging.ImageFormat.Jpeg
            Case "bmp"
                Return System.Drawing.Imaging.ImageFormat.Bmp
            Case "gif"
                Return System.Drawing.Imaging.ImageFormat.Gif
            Case "png"
                Return System.Drawing.Imaging.ImageFormat.Png
            Case "tiff"
                Return System.Drawing.Imaging.ImageFormat.Tiff
            Case "wmf"
                Return System.Drawing.Imaging.ImageFormat.Wmf
            Case "emf"
                Return System.Drawing.Imaging.ImageFormat.Emf
            Case "icon"
                Return System.Drawing.Imaging.ImageFormat.Icon
            Case "ico"
                Return System.Drawing.Imaging.ImageFormat.Icon
            Case "exif"
                Return System.Drawing.Imaging.ImageFormat.Exif
            Case Else
                Return System.Drawing.Imaging.ImageFormat.Jpeg
        End Select
    End Function
End Class