Amass + Scripting = 최고의 서브도메인 탐색

여러분들 Amass 많이 사용하시나요? Amass는 subdomain을 탐색하기 위한 도구들 중 하나로 ZAP과 마찬가지로 OWASP에 플래그쉽 프로젝트입니다. 또한 비슷한 도구인 subfinder, assetfinder, findomain 등 여러가지와 비교해봐도 거의 최고로 손꼽을 수 있는 도구입니다.

그만큼 엄청나게 많은 기능과 결과를 보장하는 도구인데요. 오늘은 Amass의 내장 스크립트 엔진인 ASE를 이용하여 Amass에서 Lua script를 통해 추가적인 로직을 구현하는 방법에 대해 이야기할까 합니다.

Amass가 어떤 도구인지 궁금하시다면 아래 글을 읽어주세요!

  • https://www.hahwul.com/cullinan/amass/
  • https://www.hahwul.com/2019/10/19/find-subdomain-takeover-with-amass-and-subjack/
  • https://www.hahwul.com/2020/09/23/amass-go-deep-in-the-sea-with-free-apis/

ASE

ASE(Amass Scripting Engine)는 위에서 이야기드렸듯이 Amass에서 Lua script를 통해 추가적인 액션을 구현할 수 있는 엔진으로 Nmap의 lua script engine과 유사합니다.

이를 통해 사용자가 직접 ASE를 이용하여 커스텀한 데이터 소스를 구현할 수 있고, 발견 도메인에 대한 추가적인 액션들(스크래핑, Pipieline 등등)을 구현하여 사용할 수 있습니다.

Using scripts

Amass의 enum 명령 중 -scripts 플래그를 사용하여 로드할 스크립트 경로나 디렉토리를 명시할 수 있습니다.

amass enum --help

....
-scripts string
    Path to a directory containing ADS scripts
...

e.g

amass enum -d hahwul.com -scripts ./subfinder.ads
in sub finder
phoenix.hahwul.com
hahwul.com

Struct of ads

스크립트는 name과 type 필드를 필수로 구성한 후 Amass event에 대한 callback function을 구현하는 형태로 만들어야 합니다.

  • name: 스크립트의 이름입니다.
  • type: 스크립트의 타입입니다. 어떤 역할을 하는지 명시할 수 있습니다.
  • callback: 스크립트의 동작을 위한 callback 들입니다. event listener 처럼 구현합니다.
  • function: 간편한 스크립팅을 위해 미리 정의된 함수들입니다.

Type field

타입은 미리 정의되어 있어서 아래 표를 참고하여 목적에 맞는 type으로 작성해주시면 됩니다.

Valid Value Category
“dns” DNS Queries
“axfr” DNS Zone Transfers
“scrape” Web Scraping
“crawl” Web Crawling
“api” Various APIs
“cert” TLS Certificates
“archive” Web Archives
“brute” Brute Forcing
“alt” Name Alterations
“guess” Name Guessing
“rir” Regional Internet Registry
“ext” External Program / Data Source

Callbacks

Amass의 event에 따라 동작한 callback에 로직은 구현해야 합니다. Amass가 event를 전달하는 callback은 아래와 같습니다.

start callback

start는 amass가 시작할 떄 호출할 callback입니다.

function start()
    set_rate_limit(1)
end

stop callback

stop은 amass가 종료될 때 호출할 callback입니다.

function stop()
    -- Cleanup code, etc.
end

vertical callback

vertical은 amass가 동작할 때 호출할 callback입니다. -d 옵션으로 전달받은 domain을 2번째 인자값으로 넘겨줍니다. 이후 로직을 처리한 후 subdomain 정보를 리턴해야 합니다. 이 때 new_name()을 통해 전달할 수 있습니다.

function vertical(ctx, domain)
    -- Send back discovered subdomain names
    new_name(ctx, name)
end

이를 통해서 subfinder, assetfinder, findomain 등 외부 서브 도메인 스캐닝 도구를 사용하여 스캔하고, 결과를 amass 결과에 포함시킬 수 있습니다.

horizontal callback

horizontal도 amass가 동작할 때 호출할 callback입니다. -d 옵션으로 전달받은 domain을 2번째 인자값으로 넘겨줍니다. 다만 horizontal은 amass가 다시 스캔할 domain의 이름을 리턴해야 합니다.

function horizontal(ctx, domain)
    -- Send back an associated domain name
    associated(ctx, domain, assoc)
end

이 기능은 서브도메인 보단, 연관 도메인을 lua script를 통해 확인하고, 이를 기반으로 다시 amass에 넘겨주어 스캔하도록 구성하기 위해 사용하는 callback입니다.

revolved callback

resolved는 dns query를 조회할 때 호출할 callback입니다.

function resolved(ctx, name, domain, records)
    crawl(ctx, "https://" .. name, 0)
end

subdomain callback

subdomain은 DNS Query를 통해 resolving이 성공했을 떄 호출할 callback입니다. 여기의 인자값으로 전달되는 도메인은 dns resolv가 성공한 도메인입니다.

function subdomain(ctx, name, domain, times)
    if times == 1 then
        crawl(ctx, "https://" .. name, 0)
    end
end

address callback

address는 IP address에 대해 프로바이드 됬을 때 호출할 callback입니다. IP를 두번째 인자값으로 넘겨줍니다.

function address(ctx, addr)
    -- Send back a related subdomain name
    new_name(ctx, name)
    -- Send back a related IP address
    new_addr(ctx, ipaddr, domain)
end

asn callback

asn은 ASN을 조회했을 때 호출할 callback입니다. 이를 통해 스캔 대상의 asn을 인자값으로 얻어올 수 있습니다.

function asn(ctx, addr, asn)
    -- Send back the related AS information
    new_asn(ctx, {
        ['addr']=addr,
        ['asn']=tonumber(asn),
        ['desc']=desc,
        prefix=cidr,
        cc="US",
        registry="ARIN",
        netblocks={cidr},
    })
end

Functions

Scripting에는 미리 정의된 함수도 존재합니다. 이를 이용하면 Amass의 다른 기능들을 lua script에서 불러와서 사용할 수 있습니다. 몇가지 중요한 function만 소개해볼게요.

log()

log() 함수는 말그대로 로그를 찍는 함수입니다. 실제 정보 전달이 목적이던, 디버깅이 목적이던 기본적인 프린팅 함수는 중요합니다.

function sendmsg(ctx, msg)
    log(ctx, msg)
end

request()

request() 함수는 웹 요청을 전송하는 함수입니다. 물론 lua script에서 직접 구현해도 되지만, 이미 잘 만들어져 있어서 가져다 쓰는것이 좋을 것 같네요.

function vertical(ctx, domain)
    local url = "https://" .. domain
    local resp, err = request(ctx, {
        method="POST",
        data=body,
        ['url']=url,
        headers={['Content-Type']="application/json"},
        id=api["key"],
        pass=api["secret"],
    })
    if (err ~= nil and err ~= "") then
        return
    end

    -- Utilize the content provided in the response
end

scrape()

scrape() 함수는 HTTP Client를 이용하여 웹 스크래핑을 진행해주는 함수입니다. 때때로 스캔한 도메인에 대해 스크래핑이 필요할 수 있는데, 미리 정의된 함수로 쉽게 구현할 수 있습니다.

function vertical(ctx, domain)
    local url = "https://" .. domain
    local ok = scrape(ctx, {
        ['url']=url,
        headers={['Accept']="text/*, text/html, text/html;level=1, */*"},
        id=api["username"],
        pass=api["password"],
    })
end

brute_wordlist()

brute scan에서 사용할 wordlist를 array 형태로 리턴하는 함수입니다.

function vertical(ctx, domain)
    local wordlist = brute_wordlist(ctx)

    for i, word in pairs(wordlist) do
        print(word)
    end
end

alt_wordlist()

alterations scan에서 사용할 wordlist를 array 형태로 리턴하는 함수입니다.

function vertical(ctx, domain)
    local wordlist = alt_wordlist(ctx)

    for i, word in pairs(wordlist) do
        print(word)
    end
end

More

이외에도 엄청 많으니.. 아래 링크를 참고해주세요!

https://github.com/OWASP/Amass/blob/master/doc/scripting.md#config-function

Examples

gist에 공개된 코드 중 괜찮은게 있어서 몇개 가져왔습니다.

scan with assetfinder

name = "assetfinder"
type = "ext"
function vertical(ctx, domain)
    print("in asset finder")
    local cmd = outputdir(ctx) .. "assetfinder --subs-only " .. domain
    local data = assert(io.popen(cmd))
for line in data:lines() do
        newname(ctx, line)
end
    data:close()
end

scan with subfinder

name = "subfinder"
type = "ext"
function vertical(ctx, domain)
    print("in sub finder")
    local cmd = outputdir(ctx) .. "subfinder -d " .. domain
    local data = assert(io.popen(cmd))
for line in data:lines() do
        newname(ctx, line)
end
    data:close()
end

References

  • https://github.com/OWASP/Amass/blob/master/doc/scripting.md
  • https://gist.github.com/sillydadddy/b1726c8e8ce281d55b82d4e2a1a610e8