์ž๋™ ํ”„๋กœ๋น„์ €๋‹ Yealink T19 + ๋™์  ์ฃผ์†Œ๋ก

์ œ๊ฐ€ ์ด ํšŒ์‚ฌ์— ์ž…์‚ฌํ–ˆ์„ ๋•Œ ์ด๋ฏธ IP ์žฅ์น˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค, ๋ณ„ํ‘œ๊ฐ€ ์žˆ๋Š” ์—ฌ๋Ÿฌ ์„œ๋ฒ„, FreeBPX ํ˜•ํƒœ์˜ ํŒจ์น˜๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ์•„๋‚ ๋กœ๊ทธ PBX Samsung IDCS500์ด ๋ณ‘๋ ฌ๋กœ ์ž‘๋™ํ–ˆ์œผ๋ฉฐ ์ผ๋ฐ˜์ ์œผ๋กœ ํšŒ์‚ฌ์˜ ์ฃผ์š” ํ†ต์‹  ์‹œ์Šคํ…œ์ด์—ˆ์œผ๋ฉฐ IP ์ „ํ™”๋Š” ์˜์—… ๋ถ€์„œ์—์„œ๋งŒ ์ž‘๋™ํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋ชจ๋“  ๊ฒƒ์ด ๊ณ„์† ์ด๋Ÿฐ ์‹์œผ๋กœ ์š”๋ฆฌ๋˜์—ˆ์„ ๊ฒƒ์ด์ง€๋งŒ ์–ด๋Š ํ™”์ฐฝํ•œ ๋‚  ๋ชจ๋“  ์‚ฌ๋žŒ์„ IP ์ „ํ™” ํ†ต์‹ ์œผ๋กœ ์ „ํ™˜ํ•˜๋ผ๋Š” ๋ฒ•๋ น์ด ๋‚ด๋ ค์กŒ๊ณ  ๋งˆ๊ฐ์ผ์ด ํ•ฉ์˜๋˜์—ˆ์œผ๋ฉฐ ์žฅ๋น„๊ฐ€ ๊ตฌ์ž…๋˜์—ˆ์œผ๋ฉฐ ๊ธฐ์—…์„ 21 ์„ธ๊ธฐ๋กœ ์ „ํ™˜ํ•˜๋ ค๋Š” ๊ณ„ํš์ด ์‹คํ–‰๋˜๊ธฐ ์‹œ์ž‘ํ–ˆ์Šต๋‹ˆ๋‹ค.
๊ทธ๋Ÿฌํ•œ ์ƒํ™ฉ์—์„œ ๊ฐ€์žฅ ๋จผ์ € ๊ฑฑ์ •๋˜๊ธฐ ์‹œ์ž‘ํ•œ ๊ฒƒ์€ ์–ด๋–ป๊ฒŒ๋“  ๊ด€๋ฆฌํ•ด์•ผ ํ•  ์ „ํ™”๊ธฐ์˜ ์ˆ˜๊ฐ€ ๊ธ‰์ฆํ•˜๊ณ  ์žˆ๋‹ค๋Š” ์ ์ด์—ˆ๊ณ , ๋‘ ๋ฒˆ์งธ๋กœ ๋งค์šฐ ๊ฑฑ์ •์Šค๋Ÿฌ์› ๋˜ ๊ฒƒ์€ ์ „ํ™”๋ฒˆํ˜ธ๋ถ€์˜€์Šต๋‹ˆ๋‹ค. Endpoint Manager๊ฐ€ ์ฒซ ๋ฒˆ์งธ ๋ฒ„์ „(์ตœ์‹  ๋ฒ„์ „์˜ FreePBX์—์„œ ์ œ์™ธ๋จ)์— ๋„์›€์„ ์ค„ ์ˆ˜ ์žˆ๋‹ค๋ฉด ์ด ์ฑ…์—์„œ ๋ช‡ ๊ฐ€์ง€ ์งˆ๋ฌธ์ด ์ƒ๊ฒผ์Šต๋‹ˆ๋‹ค.

  • ์ฒซ์งธ, ์‚ฌ์šฉ์ž์˜ ์œ„์น˜/์œ ๋™์„ฑ์ด ์ง€์†์ ์œผ๋กœ ๋ณ€ํ™”ํ•˜๋Š” ๊ฒฝ์šฐ ์ •ํ™•์„ฑ์„ ์–ด๋–ป๊ฒŒ ๋ณด์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๊นŒ?
  • ๋‘˜์งธ, ํœด๋Œ€ํฐ์„ ์™„์ „ํžˆ ๊ฐœ์ธํ™”ํ•˜๋Š” ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋งค๋ฒˆ ์—ฐ๋ฝ์ฒ˜ ์ด๋ฆ„์„ ์ž…๋ ฅํ•˜์ง€ ์•Š์œผ์‹œ๋‚˜์š”?

๋ฌธ์ œ๋Š” ํฅ๋ฏธ๋กœ์› ๊ณ  ํ•ด๊ฒฐ์ฑ…์ด ๋‚˜์˜ค๋Š” ๋ฐ๋Š” ์˜ค๋žœ ์‹œ๊ฐ„์ด ๊ฑธ๋ฆฌ์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ์ด์ œ ์ „์ฒด ๋ชฉ๋ก์„ ์ œ๊ณตํ•˜๊ณ  ์ˆœ์„œ๋Œ€๋กœ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

from scapy.all import sniff
from scapy.layers.inet import IP
import mysql.connector
import ldap
import getpass
import tftpy
import requests
import os
import time
from string import replace

def conn_ldap(login):
    ad = ldap.initialize('ldap://***.local')
    ad.simple_bind_s('voip@***.local', 'password')
    basedn = 'OU=IT,DC=***,DC=LOCAL'
    basedn_user = 'OU=***,OU=***,DC=***,DC=LOCAL'
    scope = ldap.SCOPE_SUBTREE
    filterexp = "(&(sAMAccountName=" + login + ")(ObjectClass=person))"
    filterexp2 = "(&(ObjectClass=organizationUnit))"
    attrlist = ['cn']
    attrlist2 = ['OU']
    search = ad.search_s(basedn, scope, filterexp, attrlist)
    adname = search[0][1]['cn'][0].decode('utf-8')
    if adname == ' ':
        search = ad.search_s(basedn_user, scope, filterexp2, attrlist2)
        for i in range(1, len(search)+1):
            group = search[i][1]['ou'][0]
            basedn_user2 = 'OU='+group+','+basedn_user
            search = ad.search_s(basedn_user2, scope, filterexp, attrlist)
            adname = search[0][1]['cn'][0].decode('utf-8')
            if adname != ' ':
                return adname
        adname = search[0][1]['cn'][0].decode('utf-8')
    ad.unbind_s()
    return adname


def tftp_file_change(config,place,adname,current_account,current_account_password):

    client = tftpy.TftpClient("192.168.0.3", 69)
    client.download('template.cfg', place)
    fileread = open(place, 'r')
    line = fileread.readlines()
    fileread.close()
    line[5] = (('account.1.label = ').encode('utf-8') + adname.encode('utf-8') + 'n')
    line[2] = (('account.1.auth_name = ').encode('utf-8') + current_account.encode('utf-8') + 'n')
    line[3] = (('account.1.display_name = ').encode('utf-8') + current_account.encode('utf-8') + 'n')
    line[6] = (('account.1.password = ').encode('utf-8') + current_account_password[0][0] + 'n')
    filewrite = open(place, 'w')
    for i in line:
      filewrite.write(i)
    filewrite.close()
    print place
    print config
    client.upload(config,place)


def get_phone_inform(ipaddr):
    fileconf = requests.get('http://admin:admin@'+ipaddr+'/servlet?phonecfg=get[&accounts=1]')
    conf = fileconf.text.split('|')
    current_account = conf[2]
    return current_account


def sniff_frame():
    pcapf = sniff(count=1, timeout=70, filter="dst host 192.168.0.3 and port 5060")
    if len(pcapf) == 0:
        exit()
    frame = pcapf[0]
    macaddr = frame.src
    print macaddr[:8]
    if macaddr[:8] != '80:5e:c0':
        exit()
    ipaddr = frame[0][IP].src
    return macaddr, ipaddr


def conn_mysql(query,fquery,macaddr,qwery2):
    connect = mysql.connector.connect(host='192.168.0.3', database='voip', user='voip_wr', password='***')
    cursor = connect.cursor()
    cursor.execute(fquery)
    state = cursor.fetchall()
    state = bool(state[0][0])
    if state == True:
        cursor.execute(qwery2)
        connect.commit()
        connect.close()
    else:
        cursor.execute(query)
        connect.commit()
        connect.close()


def check_account(current_account):
    connect = mysql.connector.connect(host='192.168.0.3', database='asterisk', user='voip_wr', password='***')
    cursor = connect.cursor()
    qwery = 'select data from sip where id=' + current_account + ' and keyword="secret";'
    cursor.execute(qwery)
    password = cursor.fetchall()
    if password == ' ':
        exit()
    else:
        return password


if __name__ == '__main__':
    macaddr, ipaddr = sniff_frame()
    current_account = get_phone_inform(ipaddr)
    current_account_password = check_account(current_account)
    macaddr = macaddr.replace(':', '')
    ipaddr = ipaddr.decode('utf-8')
    adname = conn_ldap(getpass.getuser())
    query = 'INSERT INTO station (mac, ip, name, number) VALUES (' + '"' + macaddr + '",' + '"' + ipaddr + '",' + '"' + adname + '",' + '"' + get_phone_inform(ipaddr) + '"' + ')'
    qwery2 = 'UPDATE station SET ip=' + '"' + ipaddr + '"' + ', name=' + '"' + adname + '"' + ', number=' + '"' + get_phone_inform(ipaddr) + '"' + ' WHERE mac=' + '"' + macaddr + '"'
    fquery = 'SELECT EXISTS(SELECT mac FROM voip.station WHERE mac=' + '"' + macaddr + '")'
    query = query.encode('utf-8')
    fquery = fquery.encode('utf-8')
    config = macaddr + '.cfg'
    place = os.path.expanduser("~") + "" + "AppDataLocal" + config
    conn_mysql(query,fquery,macaddr,qwery2)
    tftp_file_change(config,place,adname,current_account,current_account_password)
    requests.get('http://admin:admin@'+ipaddr+'/cgi-bin/ConfigManApp.com?key=AutoP')
    requests.get('http://admin:admin@'+ipaddr+'/cgi-bin/ConfigManApp.com?key=Reboot')

ํ”„๋กœ๊ทธ๋žจ์€ ์‚ฌ์šฉ์ž์˜ ์ปดํ“จํ„ฐ์—์„œ ์‹คํ–‰๋˜๋ฉฐ Yealink T19๋Š” ๊ฒŒ์ดํŠธ์›จ์ด๋กœ ์ž‘๋™ํ•  ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์— ์ปดํ“จํ„ฐ๊ฐ€ ์ „ํ™”๋ฅผ ํ†ตํ•ด ๋„คํŠธ์›Œํฌ์— ์—ฐ๊ฒฐ๋œ ๊ฒฝ์šฐ ์ž‘๋™๋ฉ๋‹ˆ๋‹ค.

๋จผ์ € ์—ฐ๊ฒฐ๋˜์–ด ์žˆ๋Š”์ง€ ์ดํ•ดํ•ด์•ผํ•ฉ๋‹ˆ๊นŒ? ๊ทธ๋ฆฌ๊ณ  ์šฐ๋ฆฌ ์ „ํ™”๊ธฐ์—๋Š” ์–ด๋–ค Mac๊ณผ IP๊ฐ€ ์žˆ๋Š”์ง€.

def sniff_frame():
    pcapf = sniff(count=1, timeout=70, filter="dst host 192.168.0.3 and port 5060")
    if len(pcapf) == 0:
        exit()
    frame = pcapf[0]
    macaddr = frame.src
    print macaddr[:8]
    if macaddr[:8] != '80:5e:c0':
        exit()
    ipaddr = frame[0][IP].src
    return macaddr, ipaddr

์—ฌ๊ธฐ์„œ๋Š” scapy ํ”„๋ ˆ์ž„์›Œํฌ์˜ sniff ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ด ๊ธฐ๋Šฅ์„ ํ†ตํ•ด ๋ฏธ๋ฆฌ ๊ฒฐ์ •๋œ udp ํŒจํ‚ท์„ ์ˆ˜์‹ ํ•˜๊ณ  70์ดˆ ๋™์•ˆ ๊ธฐ๋‹ค๋ ธ๋‹ค๊ฐ€ ์•„๋ฌด๊ฒƒ๋„ ํฌ์ฐฉํ•˜์ง€ ๋ชปํ•˜๋ฉด ์ข…๋ฃŒํ•ฉ๋‹ˆ๋‹ค.

count=1, timeout=70, filter="dst host 192.168.0.3 and port 5060"

๋‹ค์Œ์œผ๋กœ, ์žฅ์น˜๊ฐ€ ์‹ค์ œ๋กœ Yealink์ธ์ง€ ํ™•์ธํ•˜๊ณ  ํ•„์š”ํ•œ ๊ฐ’(ip ๋ฐ mac)์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

ํŠน๋ณ„ ์š”์ฒญ์„ ํ†ตํ•ด ์ „ํ™”๋กœ ํ˜„์žฌ ๊ณ„์ •์„ ์ฐพ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด ํ˜„์žฌ ๊ตฌ์„ฑ์ด ์ „ํ™”๊ธฐ์—์„œ ๋‹ค์šด๋กœ๋“œ๋˜์–ด ๊ตฌ๋ฌธ ๋ถ„์„๋ฉ๋‹ˆ๋‹ค.

def get_phone_inform(ipaddr):
    fileconf = requests.get('http://admin:admin@'+ipaddr+'/servlet?phonecfg=get[&accounts=1]')
    conf = fileconf.text.split('|')
    current_account = conf[2]
    return current_account

์ด ๊ณ„์ •์˜ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์•Œ์•„๋ณด์„ธ์š”. ์ด๋ฅผ ์œ„ํ•ด asterisk.sip ํ…Œ์ด๋ธ”๊ณผ ๊ทธ ์•ˆ์˜ ๋ฐ์ดํ„ฐ ํ•„๋“œ๋ฅผ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

def check_account(current_account):
    connect = mysql.connector.connect(host='192.168.0.3', database='asterisk', user='voip_wr', password='***')
    cursor = connect.cursor()
    qwery = 'select data from sip where id=' + current_account + ' and keyword="secret";'
    cursor.execute(qwery)
    password = cursor.fetchall()
    if password == ' ':
        exit()
    else:
        return password

๋งˆ์ง€๋ง‰ ๋‹จ๊ณ„์—์„œ๋Š” LDAP AD์— ์—ฐ๊ฒฐํ•˜๊ณ  ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด ์–ป์€ sAMAccountName์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. getpass.getuser() ํ˜„์žฌ ์‚ฌ์šฉ์ž์˜ cn์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค(์ผ๋ฐ˜์ ์œผ๋กœ ์‚ฌ์šฉ์ž์˜ ์ „์ฒด ์ด๋ฆ„์ด ํฌํ•จ๋˜์–ด ์žˆ์Œ).

def conn_ldap(login):
    ad = ldap.initialize('ldap://***.local')
    ad.simple_bind_s('voip@***.local', 'password')
    basedn = 'OU=***,DC=***,DC=LOCAL'
    basedn_user = 'OU=***,OU=***,DC=***,DC=LOCAL'
    scope = ldap.SCOPE_SUBTREE
    filterexp = "(&(sAMAccountName=" + login + ")(ObjectClass=person))"
    filterexp2 = "(&(ObjectClass=organizationUnit))"
    attrlist = ['cn']
    attrlist2 = ['OU']
    search = ad.search_s(basedn, scope, filterexp, attrlist)
    adname = search[0][1]['cn'][0].decode('utf-8')
    if adname == ' ':
        search = ad.search_s(basedn_user, scope, filterexp2, attrlist2)
        for i in range(1, len(search)+1):
            group = search[i][1]['ou'][0]
            basedn_user2 = 'OU='+group+','+basedn_user
            search = ad.search_s(basedn_user2, scope, filterexp, attrlist)
            adname = search[0][1]['cn'][0].decode('utf-8')
            if adname != ' ':
                return adname
        adname = search[0][1]['cn'][0].decode('utf-8')
    ad.unbind_s()
    return adname

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๋ฏธ๋ฆฌ ์ƒ์„ฑ๋œ ํ…Œ์ด๋ธ”(์ €๋Š” ๊ฑฐ๊ธฐ์— ์ƒ์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค)์— ์—ฐ๊ฒฐํ•˜๊ณ  ์šฐ๋ฆฌ๊ฐ€ ๋ฐฐ์šด ๋ชจ๋“  ๊ฒƒ, ์ฆ‰ ip, mac, ์‚ฌ์šฉ์ž ์ด๋ฆ„์„ ์ž…๋ ฅํ•ฉ๋‹ˆ๋‹ค.

def conn_mysql(query,fquery,macaddr,qwery2):
    connect = mysql.connector.connect(host='192.168.0.3', database='voip', user='voip_wr', password='***')
    cursor = connect.cursor()
    cursor.execute(fquery)
    state = cursor.fetchall()
    state = bool(state[0][0])
    if state == True:
        cursor.execute(qwery2)
        connect.commit()
        connect.close()
    else:
        cursor.execute(query)
        connect.commit()
        connect.close()

์ด๋ฏธ ๋™์  ์ฃผ์†Œ๋ก์„ ๋งŒ๋“ค์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ์—ฌ๊ธฐ์—์„œ ๋ฉˆ์ถœ ์ˆ˜๋„ ์žˆ์ง€๋งŒ, ์—ฌ๊ธฐ์„œ๋Š” ๋” ๋‚˜์•„๊ฐ€ ์—ฌ๊ธฐ์— ์žฅ์น˜์˜ ์ž๋™ ํ”„๋กœ๋น„์ €๋‹์„ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค.

์ด๋ฅผ ์œ„ํ•ด ์‚ฌ์ „ ๊ตฌ์„ฑ๋œ tftp ์„œ๋ฒ„์—์„œ ํ…œํ”Œ๋ฆฟ ๊ตฌ์„ฑ์„ ๋‹ค์šด๋กœ๋“œํ•˜์—ฌ ๋ณ€๊ฒฝํ•œ ํ›„ mac.cfg๋กœ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. ์ฆ‰, Yealink์—๋Š” ๋‘ ๊ฐ€์ง€ ์œ ํ˜•์˜ ๊ตฌ์„ฑ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜๋‚˜๋Š” ์ „์—ญ ๊ตฌ์„ฑ์ด๊ณ , ๋‘ ๋ฒˆ์งธ๋Š” ํŠน์ • ์ „ํ™”๊ธฐ์— ์ ์šฉ๋˜๋ฉฐ mac_phone.cfg ํ˜•์‹์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

ํŒŒ์ผ์„ ๋ชจ๋‘ ๋ณ€๊ฒฝํ•˜๊ณ  ์ด๋ฅผ ๋‹ค์‹œ tftp ์„œ๋ฒ„์— ์ €์žฅํ•œ ํ›„ ์ „ํ™”๊ธฐ์— ๋ช…๋ น์„ ๋‚ด๋ ค ์žฅ์น˜๋ฅผ ํ”„๋กœ๋น„์ €๋‹ํ•˜๊ณ  ์žฌ๋ถ€ํŒ…ํ•ฉ๋‹ˆ๋‹ค.

def tftp_file_change(config,place,adname,current_account,current_account_password):

    client = tftpy.TftpClient("192.168.0.3", 69)
    client.download('template.cfg', place)
    fileread = open(place, 'r')
    line = fileread.readlines()
    fileread.close()
    line[5] = (('account.1.label = ').encode('utf-8') + adname.encode('utf-8') + 'n')
    line[2] = (('account.1.auth_name = ').encode('utf-8') + current_account.encode('utf-8') + 'n')
    line[3] = (('account.1.display_name = ').encode('utf-8') + current_account.encode('utf-8') + 'n')
    line[6] = (('account.1.password = ').encode('utf-8') + current_account_password[0][0] + 'n')
    filewrite = open(place, 'w')
    for i in line:
      filewrite.write(i)
    filewrite.close()
    print place
    print config
    client.upload(config,place)

requests.get('http://admin:admin@'+ipaddr+'/cgi-bin/ConfigManApp.com?key=AutoP')
requests.get('http://admin:admin@'+ipaddr+'/cgi-bin/ConfigManApp.com?key=Reboot')

์žฅ์น˜๋ฅผ ์žฌ๋ถ€ํŒ…ํ•œ ํ›„ ์ „ํ™” ํ™”๋ฉด์— ์ „์ฒด ์ด๋ฆ„๊ณผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ˜•์‹์œผ๋กœ ํ•ญ์ƒ ์ •ํ™•ํ•˜๊ฒŒ ์ž…๋ ฅ๋œ ์ฃผ์†Œ๋ก์ด ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ๋‚จ์€ ๊ฒƒ์€ XML๊ณผ ์•ฝ๊ฐ„์˜ PHP๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ์ฝ˜ํ…์ธ ๋ฅผ ๋™์ ์œผ๋กœ ํ‘œ์‹œํ•˜๋Š” ๊ฒƒ๋ฟ์ž…๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌํ•œ ์˜ˆ๊ฐ€ ๋งŽ์ด ์žˆ์œผ๋ฉฐ YEALINK ์ž์ฒด์—๋„ ๊ทธ๋Ÿฌํ•œ ์˜ˆ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

์ถ”์‹ : ํ™•์žฅ์„ฑ์„ ๋†’์ด๊ธฐ ์œ„ํ•ด ๊ธฐ๋ณธ ์„ค์ •(๋ณ€์ˆ˜)์„ ๋ณ„๋„์˜ ํŒŒ์ผ๋กœ ์ด๋™ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ถœ์ฒ˜ : habr.com

์ฝ”๋ฉ˜ํŠธ๋ฅผ ์ถ”๊ฐ€