資訊安全的策略 : 深度防禦

十二月 15th, 2008 by victor No comments »

單薄的防禦

在一般常見的系統中,很常見到只要一個漏洞被發現,然後攻擊,整台電腦就跟著淪陷,為何這麼的容易,原因出在於,防線只有一道,以戰爭來比擬,你的防線可能有十公里,在這十公里中,只要有個洞就可以讓敵人鑽進來,而進來以後就有如入無人之境,以資訊安全來看,假設你的主機是網頁伺服器,你的戰線有OS、SQL Server、Web Server等等各種可能會被攻擊的點,如果你的防線就只有這樣,那麼很可能只要其中一個環節被拿下就等於整台電腦淪陷,不管你再怎麼加強每個層面,世界上沒有不存在漏洞的系統,不存在沒有漏洞的系統,那我們到底能做什麼? 答案就是 : 深度防禦。

什麼是深度防禦(Defence in depth)

深度防禦最經典的應用,應該要算是應用在核能發電廠,我們都知道核能發電廠有著一定的風險,不容許有一絲的差錯,因為核能電廠的安全是極度重要的,因此核能電廠採取就是深度防禦的策略,說了半天,到底什麼是深度防禦,所謂的深度防禦,就是一層防線後面還有一層防線,每層防線都是獨立的,多元化的、且有備援的,舉個例子來說,核能發電需要水來冷卻反應爐,如果第一套水冷系統失效,還有第二套完全獨立的系統在一旁待命,立刻可以接著運作,如果不幸的,連第二套系統也失靈了,我們還有另一道防線,將控制棒插入反應爐吸收中子,就算這層失敗了,後面還有許多許多層的防線,在很外面有一層圍阻體,就算用戰機高速衝撞也不會有事,因此核能電廠是世界上最安全的電廠,因為這一連串防線要全部失效的機率比慧星撞地球還低,每次看到一些沒道德的環保團體欺騙民眾說核電廠會像車諾比那樣炸掉來反核就很火大,回到正題,雖然主題不是核電廠,不過講到這裡我們大概就可以了解,深度防禦的策略,其思維,還有帶來的好處,與壞處,我們接著就來看在資訊安全上要如何應用

  • 多層的防護

一層防禦是不夠的,如你所見只要一層失效了,就等於淪陷,因此深度防禦的理念之一,就是提供多層的保護,一層防火牆不夠? 再加一層,還覺得不夠? 再加一層,不過當然不是加越多防護就越安全,我們繼續看下去

  • 多樣性的防護

試想你加了100層防火牆來保護你重要的資料,但這100層防火牆全用的是同一套防火牆,只要這防火牆被發現某種漏洞,這100層的防火牆很可能變得跟紙一樣,因此多樣性是必要的,例如三層不同的防火牆,以駭客的角度來看,想要進入到最裡層就需要突破三層不同的防火牆,從此看來要強行突入的可能性就大大減少

  • 獨立的組件

系統的運作最好要能盡量獨立,假設所有系統的驗證是靠另一個系統,而只要那個系統被攻破,就等於依賴那個驗證系統的所有系統也跟著一起完蛋,因此獨立性也是非常重要的

  • 備援系統

當你的系統失效時,是否有備援機制? 舉個例子,魔獸世界有通訊鎖,似乎是透過電話來驗證以防止盜帳號,前陣子突然發生通訊鎖失效,玩家抱怨進不了遊戲,於是通訊鎖似乎就因此暫停了小段時間,以攻擊者的角度來看,如果是我要盜取帳號,就可以從這裡下手,首先癱瘓它的通訊鎖,逼他們在玩家的壓力之下把通訊鎖的服務暫時關掉,不必驗證就能登入,如此一來就造成了空檔,之前盜來的帳號本來少了手機驗證這關,無法登入,但是有了這個空檔或許就可以好好利用,從這個例子我們可以看到,重要的系統需要有備援的系統,否則當這個系統失效,就產生了安全性上的問題,而且也可能造成提供攻擊的空檔,駭客可以刻意癱渙該系統以取得攻擊的空檔

  • 最小化的權限

很常見的問題之一,就是使用過大的權限去執行做一些雞毛蒜皮的事,舉個例子,使用root來執行網頁伺服器,在這種情況下,只要網頁伺服器有什麼漏洞因此被利用了,就等於整台電腦的root被拿走了是一樣的,因此使用過大的權限是危險的行為,盡量將權限限制在能做好該做的任務就好的範圍,如此一來,就算某個帳號被拿下,也難以成大事,必須尋求其它管道

  • 不要相信內部系統

即使來到系統內部,也不該相信所有資料不是惡意的,考慮一下如果駭客只拿下最外面的系統,接著要往裡面的系統攻擊,如果裡面的系統天真的以為,來自內部系統的都是安全的,那同樣等於內部的系統也被拿下,因為太相信內部系統而不做檢查的後果,因此就算來到了防線的後方,也不該因此而偷懶不用考慮安全的問題,記得假設你的每個系統都可能被攻擊,就算來自內部的系統也可能是攻擊,你怎麼知道你旁邊的電腦是不是變成殭屍了? 你怎麼知道管理員的介面就不會被殖入XSS因此而不用檢查惡意的html? 因此"不要相信任何人"

現實是…

如果都做到了,攻擊你的系統會是一件非常困難的事,一層防禦還有一層,防禦的種類又是多樣性且不盡相同,都有備援,權限被最小化,即使拿到了也只是個沒什麼用的使用者,而系統內部也不相信其它系統,拿下來外圍的系統想藉此攻擊內部也是很困難,是的,理論上來講非常困難,但是真正值得考慮的應該是到底能夠做到多少? 越多越繁複的防禦表示越高的成本,想像一下你會拿這樣的方式來防禦王小明寫給隔壁阿花的情書嗎? 我想不會,除非那個很重要,但是相對的,如果是核子彈發射的系統,這樣做就很值得,你不會想看到核子武器把玩在script kid的手中吧? 因此深度防禦如果真的要達到有深度確實要花不少成本,但是其實不用很深,我們保護的不是核子彈,就算是客戶的資料,我們一樣可以盡量增加防禦的深度,事實上不用太深的深度,當你的資料沒有那個價值讓駭客去花那麼大的力氣取得時,到那樣的深度其實就已經足夠

簡單版本的視覺化tracert

十二月 14th, 2008 by victor No comments »

我整合了一下之前的程式,做出一個簡單版本的視覺化tracert,目前因為mapnik的地圖有些功能還不能實作,不能動態去修改地圖資料,目前只能修改地圖呈現的方式,因此我以目前只能以顏色來表示router的順序,綠色是較先連到的router,紅色是較後面連到的router

視覺化顯示router所在國家

視覺化顯示router所在國家

還有不少問題要解決,原本使用的資料ip to country只能精確到國家,因此在同一個國家裡繞好幾次就看不出來,因此之後會改成使用ip to city的資料,不過檔案還真不小,就算是binary的形式也有25MB,這還算好辦,大不了開網頁解析就能線上查城市位址而不用下載所有資料,世界城市的地圖資料要從哪裡來還是另一個比較麻煩的問題

而這張圖是tracert www.google.com的結果,如大家所見的,最近不知道為什麼暴慢,hinet不知什麼原因,把google繞到日本去,這就令人匪疑所思,有人知道其中的八掛嗎? Google和hinet到底是出什麼問題呢,會讓hinet把google繞到日本去

Python實作tracert

十二月 14th, 2008 by victor 1 comment »

為了要能寫出可以在地圖上看到router所在地的程式,我用Python加上RAW_SOCKET實作了tracert的功能,封包格式的部份我直接使用這個recipe,不過好像有用到一些過時的東西會跑出警告還需要處理

import struct
import socket
 
class Packet(object):
 
    """Creates ICMPv4 and v6 packets.
 
    header
        two-item sequence containing the type and code of the packet,
        respectively.
    version
        Automatically set to version of protocol being used or None if ambiguous.
    data
        Contains data of the packet.  Can only assign a subclass of basestring
        or None.
 
    packet
        binary representation of packet.
 
    """
 
    header_table = {
                0 : (0, 4),
                #3 : (15, 4),  Overlap with ICMPv6
                3 : (15, None),
                #4 : (0, 4),  Deprecated by RFC 1812
                5 : (3, 4),
                8 : (0, 4),
                9 : (0, 4),
                10: (0, 4),
                11: (1, 4),
                12: (1, 4),
                13: (0, 4),
                14: (0, 4),
                15: (0, 4),
                16: (0, 4),
                17: (0, 4),
                18: (0, 4),
 
                1 : (4, 6),
                2 : (0, 6),
                #3 : (2, 6),  Overlap with ICMPv4
                #4 : (2, 6),  Type of 4 in ICMPv4 is deprecated
                4 : (2, None),
                128: (0, 6),
                129: (0, 6),
                130: (0, 6),
                131: (0, 6),
                132: (0, 6),
                133: (0, 6),
                134: (0, 6),
                135: (0, 6),
                136: (0, 6),
                137: (0, 6),
             }
 
    def _setheader(self, header):
        """Set type, code, and version for the packet."""
        if len(header) != 2:
            raise ValueError("header data must be in a two-item sequence")
        type_, code = header
        try:
            max_range, version = self.header_table[type_]
        except KeyError:
            raise ValueError("%s is not a valid type argument" % type_)
        else:
            if code > max_range:
                raise ValueError("%s is not a valid code value for type %s" %\
                                     (type_, code))
            self._type, self._code, self._version = type_, code, version
 
    header = property(lambda self: (self._type, self._code), _setheader,
                       doc="type and code of packet")
 
    version = property(lambda self: self._version,
                        doc="Protocol version packet is using or None if "
                            "ambiguous")
 
    def _setdata(self, data):
        """Setter for self.data; will only accept a basestring or None type."""
        if not isinstance(data, basestring) and not isinstance(data, type(None)):
            raise TypeError("value must be a subclass of basestring or None, "
                            "not %s" % type(data))
        self._data = data
 
    data = property(lambda self: self._data, _setdata,
                    doc="data contained within the packet")
 
    def __init__(self, header=(None, None), data=None):
        """Set instance attributes if given."""
        #XXX: Consider using __slots__
        # self._version initialized by setting self.header
        self.header = header
        self.data = data
 
    def __repr__(self):
        return "" % \
                (self.version, self.type, self.code, len(self.data))
 
    def create(self):
        """Return a packet."""
        # Kept as a separate method instead of rolling into 'packet' property so
        # as to allow passing method around without having to define a lambda
        # method.
        args = [self.header[0], self.header[1], 0]
        pack_format = "!BBH"
        if self.data:
            pack_format += "%ss" % len(self.data)
            args.append(self.data)
        # ICMPv6 has the IP stack calculate the checksum
        # For ambiguous cases, just go ahead and calculate it just in case
        if self.version == 4 or not self.version:
            args[2] = self._checksum(struct.pack(pack_format, *args))
        return struct.pack(pack_format, *args)
 
    packet = property(create,
                       doc="Complete ICMP packet")
 
    def _checksum(self, checksum_packet):
        """Calculate checksum"""
        byte_count = len(checksum_packet)
        #XXX: Think there is an error here about odd number of bytes
        if byte_count % 2:
            odd_byte = ord(checksum_packet[-1])
            checksum_packet = checksum_packet[:-1]
        else:
            odd_byte = 0
        two_byte_chunks = struct.unpack("!%sH" % (len(checksum_packet)/2),
                                        checksum_packet)
        total = 0
        for two_bytes in two_byte_chunks:
            total += two_bytes
        else:
            total += odd_byte
        total = (total >> 16) + (total & 0xFFFF)
        total += total >> 16
        return ~total
 
    def parse(cls, packet):
        """Parse ICMP packet and return an instance of Packet"""
        string_len = len(packet) - 4 # Ignore IP header
        pack_format = "!BBH"
        if string_len:
            pack_format += "%ss" % string_len
        unpacked_packet = struct.unpack(pack_format, packet)
        type, code, checksum = unpacked_packet[:3]
        try:
            data = unpacked_packet[3]
        except IndexError:
            data = None
        return cls((type, code), data)
 
    parse = classmethod(parse)
 
def tracert(host, callback, maxTTL=128, icmpData="Yo, what's up?"):
    import os
    host = socket.gethostbyname(host)
    # create socket
    s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.getprotobyname('icmp'))
    pid = os.getpid()
    icmp = Packet((8,0))
    icmp.data = icmpData
    for i in range(1, maxTTL):
        # set ttl
        s.setsockopt(socket.IPPROTO_IP, socket.IP_TTL, i)
        # send icmp packet
        s.sendto(icmp.packet, (host, 0))
        # receive result
        data, address = s.recvfrom(1024)
        # parse result icmp packet
        result = Packet.parse(data[20:])
        # callback
        callback(address[0], result)
        type, code = result.header
        if type == 0:
            break
 
__all__ = [
    'tracert',
    'Packet'
]
 
if __name__ == '__main__':
    def printIp(address, packet):
        print address
    tracert(raw_input('Host to trace router:'), printIp)

Mapnik整合wxPython

十二月 14th, 2008 by victor 1 comment »

什麼是Mapnik

Mapnik是一款Open source的地圖繪製的程式庫,它可以畫出非常漂亮的地圖,也可以做出像Google Map那樣的地圖,例如這個範例,不過它還算是在開發中,文件還有點不太齊全,因為我想做一套自己的VisualTracert來顯示tracert的router分部在世界的哪裡,那套VisualTracert是商業軟體,要看地圖是要錢的,因此我想弄一個Open source的版本,不過因為還很新,有的應用目前幾乎都是網頁地圖,GUI的例子很少,我花了一點時間,研究如何將地圖畫到wxPython上

程式其實不難,只是mapnik文件都還沒建好,要用dir函數和看原始碼去了解到底有什麼功能可以用,以下就是使用wxPython來畫mapnik地圖的程式

"""
This is a simple wxPython application demonstrates how to
integrate mapnik, it do nothing but draw the map from the World Poplulation XML
example:
http://trac.mapnik.org/wiki/XMLGettingStarted
 
Victor Lin. (bornstub@gmail.com)
Blog http://blog.ez2learn.com
 
"""
 
import wx
import mapnik
 
class Frame(wx.Frame):
    def __init__(self, *args, **kwargs):
        wx.Frame.__init__(self, size=(800, 500) ,*args, **kwargs)
        self.Bind(wx.EVT_PAINT, self.onPaint)
 
        self.mapfile = "population.xml"
        self.width = 800
        self.height = 500
        self.createMap()
        self.drawBmp()
 
    def createMap(self):
        """Create mapnik object
 
        """
        self.map = mapnik.Map(self.width, self.height)
        mapnik.load_map(self.map, self.mapfile)
        bbox = mapnik.Envelope(mapnik.Coord(-180.0, -75.0), mapnik.Coord(180.0, 90.0))
        self.map.zoom_to_box(bbox)
 
    def drawBmp(self):
        """Draw map to Bitmap object
 
        """
        # create a Image32 object
        image = mapnik.Image(self.width, self.height)
        # render map to Image32 object
        mapnik.render(self.map, image)
        # load raw data from Image32 to bitmap
        self.bmp = wx.BitmapFromBufferRGBA(self.width, self.height, image.tostring())
 
    def onPaint(self, event):
        dc = wx.PaintDC(self)
        memoryDC = wx.MemoryDC(self.bmp)
        # draw map to dc
        dc.Blit(0, 0, self.width, self.height, memoryDC, 0, 0)
 
if __name__ == '__main__':
    app = wx.App()
    frame = Frame(None, title="wxPython Mapnik Demo By Victor Lin")
    frame.Show()
    app.MainLoop()
wxPython整合Mapnik示範程式畫面

wxPython整合Mapnik示範程式畫面

別讓危險成為預設的行為,讓危險的行為比安全的行為更麻煩

十二月 13th, 2008 by victor 5 comments »

危險的行為

對於寫程式而言,很多預設的行為都是相當危險的,舉一些最常見的例子SQL Injection、XSS、Buffer overflow,我們可以從這些幾個最常出現被攻擊的類形,都有一個共同的特點,就是它們通常都是因為預設的行為很危險,我們一個一個來看

» Read more: 別讓危險成為預設的行為,讓危險的行為比安全的行為更麻煩

如何放置funp推文按鈕在WordPress的文章右上方

十二月 9th, 2008 by victor No comments »

似乎很多網站都這麼做,把funp推文按鈕放在文章左上角,推文章好像還蠻方便的,我就研究了一下,方法如下

首先來到funp的推文按鈕產生器,接著以

<? php the_permalink() ?>

來產生推文按鈕,選擇"多個推文按鈕"

funp推文按鈕步驟1

funp推文按鈕步驟1

接著把下面放在<head>的程式碼:

<script language="JavaScript" src="http://funp.com/tools/js/funp_button.js"></script>

複製後貼到佈景主題編輯器的header.php的如下圖所示的位置上,完成修改後記得按更新檔案

funp推文按鈕步驟2

funp推文按鈕步驟2

接著把上面欄位裡的程式碼:

<script>funp_genButton(‘<? php the_permalink() ?>’, 1);</script>

貼到如下圖所示在index.php的位置

為了讓它能夠靠右邊,我做了一點修改,在外面加上往右浮動的div,因此你也可以這樣寫,就可以得到靠右對齊的效果

<div style="float: right;"><script>funp_genButton(‘<?php the_permalink() ?>’, 1);</script></div>

修改完成後一樣按更新檔案

funp推文按鈕步驟3

funp推文按鈕步驟3

接著我們再把上面同樣的程式碼貼到single.php如下圖所示的位置上

funp推文按鈕步驟4

funp推文按鈕步驟4

修改完成後一樣按更新檔案,就大工告成了

如果你跟我用一樣的佈景應該不會有什麼問題,如果不是的話,那就得自行找出正確的插入位置囉

這年頭連垃圾廣告信都能爭眼說瞎話

十二月 9th, 2008 by victor No comments »

我還以為只有詐騙集團才能睜眼說瞎話,沒想到這年頭連網站行銷都能這樣幹,話說我已經對垃圾信沒有什麼感覺,但是還是一樣的原則,透過垃圾信行銷的網站、產品,絕對是不屑一顧

對於垃圾信是如此,對於佯裝成系統信件、朋友信件等等的垃圾信更是如此,剛才就收到這麼一封

不要臉的廣告信

不要臉的廣告信

如果您確實未曾在瘋狂賣客crazymike購買過任何東西,沒關係,但這也很有可能是我們電腦自動挑選的通知名單有誤,這表示您是多麼的幸運才會接到我們的通知。

哇~ 好幸運喔,這擺明是把所有人當白痴是嗎? 幸運個屁,連聽都沒聽過這什麼鬼網站,為什麼會有我信箱,更不可能購什麼物,加了這段文字根本更是此地無銀三百兩,還電腦自動挑選的通知名單有誤,去你的通知名單有誤,憑空生出我的信箱,不! 應該說是去買到信箱資料庫,這程式還真神奇阿,我只能說

電腦選的麻!? 我知道阿,土豆也是電腦選的

再者,就算真的沒買過東西居然會收到訂單通知信,那更讓人覺得這網站還真可靠,事關商譽的訂單信居然能這樣亂寄,哪裡來的天兵能想到這種腦殘行銷方法,請大家唾棄不要臉的不誠實的行銷方式

台中科博館的立體電影原理

十一月 30th, 2008 by victor 4 comments »

偏光濾鏡

昨天跑去台中科博館看長毛象,順便看了很久以來一直想看的立體電影,進去後才發現跟我想的不太一樣,他發的是一個紙做的眼鏡,我原本想說應該是電子式的眼鏡,用快速切換左右邊鏡片的透明與否來達到左右眼看到不一樣的影像的效果,不過猜出系統背後原理也是一種樂趣,眼鏡上面印了左右眼偏光軸的字樣和方向以及一些說明,所以它用的是互相垂直的光偏振方向來讓不同眼看見不同的影像,我們拿LCD螢幕來試試看就知道

透過眼鏡看LCD螢幕

透過眼鏡看LCD螢幕

透過眼鏡看LCD螢幕

透過眼鏡看LCD螢幕

透過眼鏡看LCD螢幕

透過眼鏡看LCD螢幕

透過眼鏡看LCD螢幕

透過眼鏡看LCD螢幕

透過眼鏡看LCD螢幕

透過眼鏡看LCD螢幕

因為有些液晶螢幕的顯像原理就是這利用光的偏振軸的旋轉,某些液晶的特性,是施加不同的電壓,光透過的偏振軸就會被扭轉,而在外面加上一層這樣的濾光鏡,就可以用電壓控制透過的光的亮度,偏振軸和濾鏡的濾光方向越是平行,所能透過的光就越多,越是和濾鏡的濾光方向垂直,其透過的光就越少,加上顏色濾鏡,藉此就能控制不同的顏色亮度,因為LCD本身就是以這種方式顯像,所以其實所有透出來的光都是同一個偏振方向,左眼的濾鏡剛好是左上右下的方向,可以看到的光線最多,表示LCD的濾光鏡所能透過的方向就是這個方向,而右眼剛好是垂直,因此看到的影像是黑色的,也就是幾乎沒有光線透過去

立體電影

而立體電影就是利用這樣的原理讓觀眾的左眼和右眼看到不同的影像,在左眼影像的投影機的前面加上左眼偏光方向的濾鏡,在右眼影像的投影機上加上右眼偏光方向的濾鏡,雖然還有很多細節要考慮,不過大至上的原理就是這樣,如此一來左眼就只看到左眼的影像,右眼就只看到右眼的影像

左眼看到的影像

左眼看到的影像

右眼看到的影像

右眼看到的影像

pygame隨便玩

十一月 28th, 2008 by victor No comments »

什麼是pygame?

pygame是一款用python寫的遊戲函式庫,基於SDL之上,之前就很想要玩玩看,今天隨便找了一個範例然後隨手亂改一番

pygame隨便玩的demo抓圖

pygame隨便玩的demo抓圖

心得就是pygame寫起來很順手、很簡單,改天有空的話用它來寫個Game試試

亂寫的demo可以在這裡下載

pygame亂寫的demo

也沒什麼特別的東西,只有會繞著滑鼠旋轉的圖,只要安裝python直譯器和對應版本的pygame就可以執行

好玩的益智解謎遊戲 : 黏黏世界 (World Of Goo)

十一月 23rd, 2008 by victor No comments »

黏黏世界

最近發現一款很有趣的遊戲,叫做黏黏世界 (World Of Goo),它與眾不同的地方在於,它使用物理模擬分子結構來通過每個關卡,也就是玩家要透過組合這些小分子,在遊戲裡面叫做Goo的一種生物,來抵達吸管,每個關卡都有個門檻,至少要吸多少個才算過關,但是當然吸超過越多越好,換言之,也使用最少的分子來過關,這是它的遊戲畫面

World of goo 主選單

World of goo 主選單

World of goo 選擇關卡畫面

World of goo 選擇關卡畫面

World of goo 第一關畫面

World of goo 第一關畫面

» Read more: 好玩的益智解謎遊戲 : 黏黏世界 (World Of Goo)