โปรเจคเขียนเล่น (ใช้จริง): After-Access Antivirus

  • warning: realpath() [function.realpath]: SAFE MODE Restriction in effect. The script whose uid is 1005 is not allowed to access /tmp owned by uid 0 in /var/www/sites/sugree/codenone.com/subdomains/www/html/includes/file.inc on line 190.
  • warning: realpath() [function.realpath]: SAFE MODE Restriction in effect. The script whose uid is 1005 is not allowed to access /tmp owned by uid 0 in /var/www/sites/sugree/codenone.com/subdomains/www/html/includes/file.inc on line 190.

อันนี้เคยเขียนไว้ในบล็อกตัวเองมาสักพักแล้วครับ เริ่มจากความว่าผมเองต้องดูแลเครื่องที่แชร์ไฟล์ผ่าน Samba ค่อนข้างเยอะ เลยมีคำถามว่าจะป้องกันไวรัสกันยังไง

ก่อนหน้านี้ลินุกซ์มีระบบป้องกันไวรัสไว้ค่อนข้างดีอยู่แล้ว โดยมากแล้วมักเป็นแนวคิดแบบ On-Access Antivirus คือก่อนที่จะส่งข้อมูลในไฟล์ให้กับผู้ใช้ ก็เอามาตรวจไวรัสกันซะก่อน ตัวอย่างก็เช่น Samba-VScan หรือ ClamFS

กรณีของผมนี้ ผมยอมรับความเสี่ยงที่จะไม่ป้องกันไวรัสสำหรับลินุกซ์ เพราะผมเชื่อว่าความเสี่ยงต่ำพอ ความต้องการที่ผมคิดออกมาได้เป็นดังนี้

  • ไม่ลดประสิทธิภาพการทำงานรวม แนวคิด On-Access สอบตกหมด เพราะลดประสิทธิภาพการเข้าถึงไฟล์
  • เน้นการป้องกันการเป็นพานะ ผมยินดีรับความเสี่ยงกับไวรัสบนลินุกซ์ แต่ไม่ต้องการให้ลินุกซ์เป็นพาหะแพร่ไวรัสไปยังเครื่องจำนวนมากที่ Map Drive ไว้

แนวคิดในการออกแบบเลยเป็นแนวคิดง่ายๆ ว่า ให้สแกนทุกไฟล์หลังจากมีการเปลี่ยนแปลงลงดิสก์ ถ้าติดไวรัสให้ย้ายไฟล์นั้นออกจากส่วนแชร์ไฟล์

หลังจากหาข้อมูลใน twitter อยู่สิบนาที คำตอบสุดท้ายที่ได้มาจาก @sugree คือการใช้ inotify เพื่อตรวจสอบว่ามีการเขียนไฟล์ใดไปแล้วบ้าง สำหรับ Python ก็มี pyinotify ไว้ให้ใช้เสร็จสรรพ ส่วนการจับไวรัสนั้นก็สามารถใช้ ClamAV ผ่านทาง pyclamd ได้

เขียน Proof-of-Concept ออกมาได้ในเวลาไม่ถึงชั่วโมง โดยอาศัย tutorial จาก pyinotify และ pyclamd

import os
import shutil as sh
import pyclamd as cav
from pyinotify import WatchManager, Notifier, ThreadedNotifier, EventsCodes, ProcessEvent
 
#ประกาศ socket อันนี้จะแตกต่างไปตาม OS ในกรณีนี้เป็นของ Ubuntu 7.10
cav.init_unix_socket('/var/run/clamav/clamd.ctl')
 
wm = WatchManager()
#ตรวจสอบเฉพาะการสร้างและการแก้ไขไฟล์ ถ้าเปิดมาอ่านเฉยๆ ก็ปล่อยไป
mask = EventsCodes.IN_CREATE | EventsCodes.IN_MODIFY 
 
class PAfterAccess(ProcessEvent):
    def process_IN_CREATE(self, event):
        fname = os.path.join(event.path, event.name)
        self.do_scan(fname)
 
    def process_IN_MODIFY(self, event):
        fname = os.path.join(event.path, event.name)
        self.do_scan(fname)
 
    def do_scan(self, fname):
        res = None
        try:
            res = cav.scan_file(fname)
            print "Scanned %s result is %s" % (fname,str(res))
        except cav.ScanError:
            #ScanError มักพบในกรณีที่ไฟล์นั้นหายไปแล้ว เช่นกรณีไฟล์ Swap
            print "Can't scan file %s" % fname
        #ฟังก์ชั่น scan_file จะคืนค่าเป็น None ถ้าไม่เจอไวรัส
        #ในกรณีนี้ถ้าเจอไวรัสก็ให้ย้ายไฟล์ไปเก็บที่อื่น
        if res != None:
            hname = fname[1:].replace("/","-")
            sh.move(fname,"/home2/quarantine/"+hname)
 
 
notifier = Notifier(wm, PAfterAccess())
#ระบุตำแหน่งที่ต้องการแสกน ควรตั้ง auto_add=True ไว้ด้วยเผื่อการสร้าง folder ใหม่
wdd = wm.add_watch('/home', mask, rec=True,auto_add=True)
 
#ที่เหลือยกมาจาก Tutorial
while True:  # loop forever
    try:
        # process the queue of events as explained above
        notifier.process_events()
        if notifier.check_events():
            # read notified events and enqeue them
            notifier.read_events()
        # you can do some tasks here...
    except KeyboardInterrupt:
        # destroy the inotify's instance on this interrupt (stop monitoring)
        notifier.stop()
        break

ผมยังไม่ได้วัดว่าการทำแบบนี้จะช่วยเพิ่มประสิทธิภาพการเข้าถึงไฟล์ได้หรือไม่ หรือจะเพิ่มประสิทธิภาพได้มากแค่ไหนเทียบกับ On-Access แบบเดิมๆ แต่เท่าที่ทดสอบดูก็พบว่าการใช้งานแบบนี้ก็สะดวกในการติดตั้งกว่าการสแกนแบบ On-Access ค่อนข้างมาก โดยเพียงแค่ให้สคริปต์นี้รันตอนบูทเครื่องเท่านั้น ไม่ต้องแก้ไข fstab หรือติดตั้งโมดูลใน samba ให้ยุ่งยาก

ไว้ต้องวัดประสิทธิภาพกับทำ performance tuning กันอีกที

sugree's picture

สั้นดีแฮะ เคยใช้ inotify กับ on-line synchronization

เยี่ยม
ว่าแล้วก็หาทางเอา WatchManager ไปใช้บ้างดีกว่า

ของ Ruby ผมเคยใช้ filesystemwatcher ก็ตรงไปตรงมาดีครับ

ได้ทางเลือกเพิ่มขึ้น ขอบคุณครับ

ย้าย Codenone

ประกาศย้าย Codenone ไปใช้ Forum ของ Blognone แทนครับ ตามไปตั้งกระทู้ต่อได้ที่ Codenone Forum (รายละเอียดอ่านจากกระทู้ ย้าย Codenone ไปรวมกับ Blognone)

กระทู้เก่าๆ จะย้ายตามไปในภายหลัง ตอนนี้ปิดการโพสต์กระทู้ไว้ เหลือไว้เฉพาะอ้างอิงเท่านั้น