เนื้อหาตอนนี้จะพูดถึงการใช้งาน security class ที่มีมาใน codeigniter ซึ่งเราสามารถนำมาใช้
กับ web application ของเรา เพื่อเพิ่มความปลอดภัย ในการรับข้อมูลจากผู้ใช้
ซึ่งโดยปกติทั่วไป ก็จะเป็นหน้าที่ผู้ใช้ทั่วไปสามารถที่จะส่งข้อมูล เพื่อบันทึกลงฐานข้อมูล
หรืออื่นๆ เช่น ระบบกระดานสนทนา การส่งข้อมูลอื่นๆ
แต่ในที่นี้เราจะประยุกต์กับส่วนของหน้า admn หรือหน้าผู้ดูแลระบบ สามารถนำไปต่อยอด
หรือปรับใช้กับส่วนของผู้ใช้งานทั่วไปได้
ขอยกเนื้อหาจากตอนที่แล้วมาเป็นแนวทางประกอบการอธิบาย และศึกษาไปพร้อมกันๆ
การใช้งาน XSS Filtering
ใน codeigniter จะมีฟังก์ชั่นสำหรับจัดการกับ XSS หรือการ hack รูปแบบ Cross Site Scripting
แนวๆ ฝัง script ลงในเนื้อหาภายในเว็บไซต์ ผ่านช่องทางที่ผู้ใช้งานสามารถ ส่งค่าข้อมูลเพื่อ
บันทึกได้ (ผู้เขียนไม่ได้มีความรู้โดยตรงในส่วนนี้อย่างละเอียด ใครต้องการความรู้เพิ่ม หาอ่านได้
ตามเว็บไซต์ต่างๆ) ส่วนมากเกิดจากระบบการบันทึกและตรวจสอบข้อมูลก่อนการบันของเว็บไซต์
ไม่มีการจัดการที่ดีพอ ก็อาจเกิดเป็นช่องโหว่ของกรณีข้างต้นนี้ได้
เท่าที่ดูตามคู่มือ พบว่า เราสามารถกำหนดตรวจสอบและเรียกใช้ฟังก์ชั่น จัดการกับ XSS โดย
ไปตั้งค่าได้ในไฟล์ config.php ในโฟลเดอร์ apps > config
$config['global_xss_filtering'] = FALSE;
โดยเปลี่ยนค่าเป็น TRUE
แต่รู้สึกว่า codeigniter มองว่าไม่มีความจำเป็นในการกำหนดในส่วนนี้ และจะทำการยกเลิกการใช้งาน
ในอนาคต โดยให้เหตุผลว่า ไม่จำเป็นที่ข้อมูล GET, POST หรือ COOKIE จะต้องถูกเรียกใช้ฟังก์ชั่นนี้
ทุกๆ ค่า และให้ความสำคัญไปที่การตรวจสอบ การแสดงข้อมูล มากกว่าข้อมูลที่บันทึก หมายความว่า
เราสามารถจะป้องกัน XSS ในขั้นตอนการนำข้อมูลมาแสดง โดยสามารถใช้ฟังก์ชั่น php ทั่วไปตัดการทำงาน
ในส่วนของ script ออกไปแทนได้
ดังนั้นในที่นี้เราจะไม่ไปกำหนดในส่วนของค่า global_xss_filtering ให้เป็น TRUE
แต่จะให้เป็น FALSE เหมือนเดิม
$config['global_xss_filtering'] = FALSE; // ไม่ต้องเปลี่ยนเป็น TRUE
นอกจากนั้น codeigniter แนะนำให้เรียกใช้ฟังก์ชั่นตรวจสอบ XSS เฉพาะข้อมูลที่เราต้องการเท่านั้น
แทนการกำหนดให้กับทุกข้อมูลที่รับเข้ามา โดยเราสามารถทำได้ ดังจะอธิบายต่อไป
มาที่ไฟล์ admin_aboutus.php ในโฟลเดอร์ apps > views > admin
ไฟล์ดังกล่าวนี้ เรามีการใช้งาน CKEditor ซึ่งรองรับการเก็บข้อมูลในรูปแบบ HTML
รวมถึงรองรับการแทรก javascript เข้าไปได้ แต่โดยค่าเริ่มต้น แล้วระบบของ CKEditor จะมีระบบการ
จัดการกับ XSS อยู่ในระดับหนึ่ง คือถ้าเราไม่ได้เปิดใช้งานให้สามารถแทรก script ได้ ตัว CKEditor ก็จะแปลง
ส่วนของ script เป้นข้อความว่า [removed] หรืออื่นๆ เป็นต้น
อย่างไรก็ตาม ก็ยังไม่เพียงพอ เพราะบางกรณี hacker อาจจะมีการส่งข้อมูลผ่านโปรแกรมอื่น
หรือตัวช่วยอื่น เข้ามาแทนได้ ดังนั้นส่วนของโปรแกรมก็จำเป็นในการเพิ่มความปลอดภัยในการตรวจสอบ
ข้อมูลนั้นก่อนที่จะทำการบันทึก
เรามาทดสอบเบื้องต้น ให้กำหนดให้ CKEditor ของเราให้รองรับการรับค่า ที่เป็น javascript ได้
โดยให้ไปที่ไฟล์ config.js ในโฟลเดอร์ js > ckeditor
เพิ่มในส่วนของตั้งค่า ดังนี้
config.allowedContent = true;
จะได้เป็น
CKEDITOR.editorConfig = function( config ) { config.language = 'th'; config.uiColor = '#F7F7F7'; config.allowedContent = true; };
จากนั้นในไฟล์ admin_aboutus.php ให้เราเพิ่มค่าต่อท้ายไปในไฟล์ การเรียกใช้งานตั้งค่าของ config.js ดังนี้
<script> CKEDITOR.replace( 'aboutus_detail', { customConfig:'<?=base_url('js/ckeditor/config.js')?>?<?=time()?>' }); </script>
เหตุผลที่ต้องต่อท้ายส่วนของไฟล์ ด้วย ?<?=time()?> เพื่อให้ไฟล์ js นั้นมีการอัพเดท เพราะส่วนใหญ่เราจะพบปัญหา
เวลาแก้ไขไฟล์ js ในการทดสอบ ค่าในบราวเซอร์จะชอบจำค่าเก่าหรือค่าแคช ดังนั้นเพื่อให้สะดวกในการทดสอบจึง
ต้องเพิ่มส่วนนี้เข้าไปเพื่อให้ไฟล์มีการอัพเดทหรือดึงค่าใหม่ทุกครั้งที่มีการเรียกใช้ แต่กรณีใช้งานจริงเราตัดส่วนนี้ออกได้
ต่อไปเราจะลองเพิ่ม script เข้าไปใน CKEditor โดยให้เปลี่ยนตรง toolbar เป้น ดูรหัส HTML (Source) ก่อนแล้วแทรก
โค้ดนี้ลงไป
<script>alert('ok');</script>
แล้วทำการกดปุ่ม อัพเดทข้อมูล
ซึ่งจะเห็นว่า script จะแสดงใน CKEditor และสามารถถูกเรียกใช้งานผ่านหน้าแสดงข้อมูลได้
และกรณีแบบนี้เมื่อ hacker แทรกคำสั่งที่จะดูข้อมูลบางอย่าง เช่น คัวตัวแปร cookie ข้อมูล session id หรืออื่นๆ
ก็อาจจะมีค่ำสั่งให้ส่งค่าต่างๆ เหล่านี้กลับไปได้
ทีนี้เรามาใช้ฟังก์ชั่น ในการจัดการกับรูปแบบการ hack ข้อมูลแบบ XSS ดังนั้
โดยในขั้นตอนการบันทึกข้อมูล เราจะเรียกใช้ฟังก์ชั่น
$data = $this->security->xss_clean($data);
ตัวอย่างการประยุกต์ใช้กับโค้ดหน้าอัพเดทข้อมูลของไฟล์ admin_aboutus.php
โค้ดเดิม
<?php if($this->input->post('save_data')){ // เมื่อทำการกดปุ่ม sumit $newdata = array( 'aboutus_title' => $this->input->post('aboutus_title'), 'aboutus_detail' => $this->input->post('aboutus_detail'), 'aboutus_update' => date("Y-m-d H:i:s") ); $this->db->update('tbl_aboutus', $newdata,array('aboutus_id'=>1)); redirect('admin/aboutus'); // ไปหน้าเดิม หรือก็ือให้รีโหลด } ?>
จะได้เป็น
<?php if($this->input->post('save_data')){ // เมื่อทำการกดปุ่ม sumit $p_aboutus_title = $this->security->xss_clean($this->input->post('aboutus_title')); $p_aboutus_detail = $this->security->xss_clean($this->input->post('aboutus_detail')); $newdata = array( 'aboutus_title' => $p_aboutus_title, 'aboutus_detail' => $p_aboutus_detail, 'aboutus_update' => date("Y-m-d H:i:s") ); $this->db->update('tbl_aboutus', $newdata,array('aboutus_id'=>1)); redirect('admin/aboutus'); // ไปหน้าเดิม หรือก็ือให้รีโหลด } ?>
จากนั้นให้เราทำการกดปุ่ม อัพเดทข้อมูล จะพบว่าส่วนของ javascript จะถูกแปลงเป็นรูปบบที่ไม่สามารถรันได้
ตามรูปด้านล่าง
นอกจากนั้ เรายังสามารถตรวจสอบ สำหรับในส่วนของการอัพโหลดไฟล์รูปภาพ ว่ามีการส่งข้อมูลมาโจมตีทาง
ช่องทางนี้หรือไม่ โดยใช้รูปแบบคำสั่งดังนี้
if ($this->security->xss_clean($file, TRUE) === FALSE) { // ไฟล์ไม่ผ่านการตรวจอสอบ XSS }
มีประใยชน์ในการตรวจสอบไฟล์รูปภาพ ก่อนทำการอัพโหลด คือถ้าค่าเป็น FALSE แสดงว่ารูปภาพที่อัพโหลดมี
ข้อมูลหรือคำสั่งที่มุ่งร้ายหรือเป็นอันตรายถ้าบราวเซอร์ทำการันหรือเรียกใช้ไฟล์นั้นๆ
การตรวจสอบ Cross-site request forgery (CSRF)
เป็นลักษณะการ hack ข้อมูลโดยเรียกใช้งานจากเว็บไซต์อื่น หรือจากภายนอกเว็บไซต์เราอีกที
ตัวอย่างเข่น การส่งข้อมูลจากโปรแกรมอื่นยิงมายังเว็บไซต์ของเรา เพื่อทำการเพิ่มข้อมูล คล้ายๆ
ลักษณะ spam ข้อมูล หรือทำเสมือนเป้นผู้ใช้งานทั่วไปของเว็บไซต์ จากนั้นทำการจำลองข้อมูล
ที่จำเป้น แล้วทำการส่งข้อมูลนั้นเข้ามาในระบบเว็บไซต์ของเรา เพื่อทำงานบางอย่าง (ผู้เขียนมีความ
เข้าใจในส่วนนี้ไม่มาก ให้ทำการหาข้อมูลเพิ่มเติมเกี่ยวกับ CSRF ตามเว็บไซต์ต่างๆ)
ในรายละเอียดต่างเกี่ยวกับ CSRF คงไม่ได้ลงไปมาก เรามาดูในเรื่องของการป้องกัน
โดยเราสามารถเปิดใช้งานการป้องกันการ hack แบบ CSRF ดังนั้
ให้เปิดไฟล์ config.php ในโฟลเดอร์ apps > config
$config['csrf_protection'] = FALSE; $config['csrf_token_name'] = 'csrf_test_name'; $config['csrf_cookie_name'] = 'csrf_cookie_name'; $config['csrf_expire'] = 7200; $config['csrf_regenerate'] = TRUE; $config['csrf_exclude_uris'] = array();
โดยปรับค่าเป็นดังนี้ (หรือกำหนดตามแบบของตัวเองได้)
$config['csrf_protection'] = TRUE; // กำหนดเป็น TRUE เพื่อเปิดใช้งาน $config['csrf_token_name'] = 'csrf_tk_name'; // กำหนดชื่อสำหรับตัว input hidden $config['csrf_cookie_name'] = 'csrf_ck_name'; // กำหนดชื่อสำหรับส่วนของ cookie $config['csrf_expire'] = 7200; // เวลาหมดอายุ วินาที $config['csrf_regenerate'] = TRUE; // ให้เปลี่ยนค่าใหม่เสมอ $config['csrf_exclude_uris'] = array(); // url ที่ไม่ต้องการตรวจอบ CSRF
จากนั้นในไฟล์ admin_aboutus.php ในโฟลเดอร์ apps > views >admin
ให้เราเพิ่มส่วนของการเรียกใช้งานต่อไปนีแทรกต่อไปในส่วนของแท็ก form
<?php $csrf = array( 'name' => $this->security->get_csrf_token_name(), 'hash' => $this->security->get_csrf_hash() ); ?> <input type="hidden" name="<?=$csrf['name'];?>" value="<?=$csrf['hash'];?>" />
ดังนั้นในไฟล์ admin_aboutus.php จะได้เป็น
<div class="container"> About Us <br><br> <?php if($this->input->post('save_data')){ // เมื่อทำการกดปุ่ม sumit $p_aboutus_title = $this->security->xss_clean($this->input->post('aboutus_title')); $p_aboutus_detail = $this->security->xss_clean($this->input->post('aboutus_detail')); $newdata = array( 'aboutus_title' => $p_aboutus_title, 'aboutus_detail' => $p_aboutus_detail, 'aboutus_update' => date("Y-m-d H:i:s") ); $this->db->update('tbl_aboutus', $newdata,array('aboutus_id'=>1)); redirect('admin/aboutus'); // ไปหน้าเดิม หรือก็ือให้รีโหลด } ?> <?php $query = $this->db->get_where("tbl_aboutus",array("aboutus_id"=>1)); $row = $query->row_array(); //คิวรี่ข้อมูลมาแสดงแค่รายการเดียว if(isset($row)) // ถ้ามีข้อมูล { ?> <form action="<?=base_url('admin/aboutus')?>" method="post"> <?php $csrf = array( 'name' => $this->security->get_csrf_token_name(), 'hash' => $this->security->get_csrf_hash() ); ?> <input type="hidden" name="<?=$csrf['name'];?>" value="<?=$csrf['hash'];?>" /> <table class="table"> <tr> <th>หัวข้อ: </th> <tr> </tr> <td> <input type="text" name="aboutus_title" class="form-control" value="<?=$row['aboutus_title']?>"> </td> </tr> <tr> <th>รายละเอียด: </th> </tr> <tr> <td> <textarea name="aboutus_detail" rows="10" class="form-control"><?=$row['aboutus_detail']?></textarea> </td> </tr> <tr> <td><input type="submit" value="Update Data" name="save_data" class="btn btn-primary"></td> </tr> </table> </form> <?php } ?> <script src="//cdn.ckeditor.com/4.5.3/standard/ckeditor.js"></script> <script> CKEDITOR.replace( 'aboutus_detail', { customConfig:'<?=base_url('js/ckeditor/config.js')?>' }); </script> </div>
หมายเหตุ: การเปิดใช้งาน $config['csrf_protection'] = TRUE; จะมีผลให้การ submit ข้อมูล
ในรูปแบบปกติของเรา จะเกิดการ error ขึ้น คือจะมีผลทั้งโปรเจ็ค
แต่เราสามารถกำหนดได้ว่า ไม่ต้องทำงานใน url ไหนบ้าง โดยกำหนดได้ที่
$config['csrf_exclude_uris'] = array();
ตัวอย่างเช่น
$config['csrf_exclude_uris'] = array( 'admin/login', 'admin/user/[a-z]+/[0-9]+', 'admin/service/[a-z]+/[0-9]+', );
แต่การจะกำหนดในลักษณะนี้ เพื่อตัดส่วนของ url หน้าที่ไม่ต้องการใช้งานการตรวจสอบ CSRF
และการไปไล่แทรกส่วนของ hidden ให้กับฟอร์มทุกอันที่เราจะใช้งาน
คงไม่ใช้วิธีที่ดีแน่ๆ และอีกอย่าง codeigniter ก็แนะนำว่า ควรเปิดใช้งาน CSRF
เราจึงจะใช้วิธีใช้งานจาก form helper และใช้งานฟังก์ชั่น form_open() แทนการ
กำหนดแท็ก form แบบปกติ
การโหลด Form Helper และ ใช้งาน form_open() เพื่อรองรับการตรวจสอบ CSRF
ให้เราเปิดไฟล์ autoload.php ในโฟลเดอร์ apps > config
ให้เพิ่ม form เข้าไป ดังนี้
$autoload['helper'] = array('url','form');
และก็ในไฟล์ config.php ในโฟลเดอร์ apps > config
ในส่วนของ $config['csrf_exclude_uris'] เราไม่ต้องกำหนด url ให้ไช้เป็นดังนี้
$config['csrf_exclude_uris'] = array();
จะได้เป็น
$config['csrf_protection'] = TRUE; $config['csrf_token_name'] = 'csrf_tk_name'; $config['csrf_cookie_name'] = 'csrf_ck_name'; $config['csrf_expire'] = 7200; $config['csrf_regenerate'] = TRUE; $config['csrf_exclude_uris'] = array();
จากนั้นมาที่ไฟล์ admin_aboutus.php ในโฟลเดอร์ apps > views >admin
ให้ตัดส่วนโค้ดข้างต้นที่เพิ่มเข้ามาออก
<?php $csrf = array( 'name' => $this->security->get_csrf_token_name(), 'hash' => $this->security->get_csrf_hash() ); ?> <input type="hidden" name="<?=$csrf['name'];?>" value="<?=$csrf['hash'];?>" />
ตัวโค้ดตามด้านบนออก
จากนั้นเปลี่ยนแท็ก form จาก
<form action="<?=base_url('admin/aboutus')?>" method="post">
เป็น
<?php echo form_open('admin/aboutus'); ?>
ฟังก์ชั่นนี้จะสร้างโค้ดให้เราตามนี้
<form action="http://localhost/learnci/admin/aboutus" method="post" accept-charset="utf-8"> <input type="hidden" name="csrf_tk_name" value="1956f95c31af0f4493ae10c0e9be33dc" style="display:none;" />
จะเห็นว่า การใช้ฟังก์ชั่น form_open() จะมีการสร้าง hidden สำหรับส่งค่าเพื่อตรวจสอบ CSRF ให้เรา
อัตโนมัติ และค่า จะเปลี่ยนไปเรื่อยๆ
เรามาดูการกำหนดค่าเพิ่มเติม การใช้งาน form_open() กัน
<?php echo form_open('admin/aboutus'); ?>
จะได้รูปแบบ
<form action="http://localhost/learnci/admin/aboutus" method="post" accept-charset="utf-8">
การเพิ่ม Attributes สามารถกำหนดได้ดังนี้
<?php $attributes = array('class' => 'email', 'id' => 'myform'); echo form_open('admin/aboutus', $attributes); ?>
หรือจะกำหนดแบบนี้
<?php echo form_open('admin/aboutus', 'class="form-control" id="myform"' ); ?>
จะได้รูปแบบ
<form accept-charset="utf-8" action="http://localhost/learnci/admin/aboutus" method="post" class="form-control" id="myform">
การเพิ่ม hidden input field
<?php $hidden = array('data1' => 'AAA', 'data2' => '234'); echo form_open('admin/aboutus','', $hidden); ?>
จะได้รูปแบบ
<form action="http://localhost/learnci/admin/aboutus" method="post" accept-charset="utf-8"> <input type="hidden" name="data1" value="AAA" /> <input type="hidden" name="data2" value="234" />
กรณีมีการอัพโหลดไฟล์ จะใช้ฟังก์ชั่น form_open_multipart()
ตัวอย่าง
<?php echo form_open_multipart('admin/aboutus'); ?>
จะได้รูปแบบ
<form action="http://localhost/learnci/admin/aboutus" enctype="multipart/form-data" method="post" accept-charset="utf-8">
เมื่อเราพอเข้าใจรุปแบบการใช้งาน ฟังก์ชั่น form_open() และ form_open_multipart() เบื้องต้นแล้ว
ให้เราปรับใช้กับทุกไฟล์ของโปรเจ็คเราที่มีการใช้งานฟอร์ม
ไฟล์ admin_login.php ในโฟลเดอร์ apps > views > admin
<form action="<?=base_url('admin/login')?>" method="post">
เปลี่ยนเป็น
<?php echo form_open('admin/login'); ?>
ไฟล์ admin_user.php ในโฟลเดอร์ apps > views > admin
<form action="<?=base_url('admin/user/create')?>" method="post">
เปลี่ยนเป็น
<?php echo form_open('admin/user/create'); ?>
และ
<form action="<?=base_url('admin/user/edit/'.$id)?>" method="post">
เปลี่ยนเป็น
<?php echo form_open('admin/user/edit/'.$id); ?>
ไฟล์ admin_service.php ในโฟลเดอร์ apps > views > admin
<form class="form-horizontal" action="<?=base_url('admin/service')?>" method="get">
เปลี่ยนเป็น
<?php echo form_open('admin/service',array('class'=>'form-horizontal','method'=>'get') ); ?>
จะเห้นว่าส่วนนี้จะมีการส่งค่าเป็น get ไม่มีการสร้าง hidden เพื่อตรวจสอบ CSRF
<form action="<?=base_url('admin/service/create')?>" method="post" enctype="multipart/form-data">
เปลี่ยนเป็น
<?php echo form_open_multipart('admin/service/create'); ?>
ส่วนนี้มีการอัพโหลดไฟล์ เราใช้ฟังก์ชั่น form_open_multipart() แทน
และ
<form action="<?=base_url('admin/service/edit/'.$id)?>" method="post" enctype="multipart/form-data">
เปลี่ยนเป็น
<?php echo form_open_multipart('admin/service/edit/'.$id); ?>
เป้นอันเสร็จเรียบร้อยกับการปรับการเรียกใช้ form ให้รองรับ CSRF
ดังนั้นถ้าเราเริ่มโปรเจ็คใหม่ จำไว้ว่าควรเปิดใช้งาน form helper ใน autoload.php และ
เรียกใช้งาน form_open() แทนการกำหนด แท็ก form แบบปกติ เพื่อให้สามารถรองรับ
การเปิดใช้งานการตรวจสอบ CSRF ได้