Repository files navigation

httpimport

Python's missing feature!

Remote, in-memory Python package/moduleimporting through HTTP/S

PyPI version

A feature that Python2/3misses and has become popular in other languages is the remote loading of packages/modules.

httpimport lets a Python2/3 packages/modules to be imported directly in Python interpreter's process memory, through remote URIs, and more...

Example - In a Nutshell

>>>importhttpimport>>>httpimport.__all__
['HttpImporter', 'add_remote_repo', 'remove_remote_repo', 'remote_repo', 'github_repo', 'bitbucket_repo']
>>>withhttpimport.remote_repo(['package1','package2','package3'], 'http://my-codes.example.com/python_packages'):
... importpackage1
...
>>>withhttpimport.github_repo('operatorequals', 'covertutils', branch=master):
... importcovertutils
... # Also works with 'bitbucket_repo'
>>># A depends to B and B depends to C (A, B, C : Python modules/packages in different domains):>>># A exists in "repo_a.my-codes.example.com"	|>>># B exists in "repo_b.my-codes.example.com" | <-- Different domains>>># C exists in "repo_c.my-codes.example.com" |>>>withhttpimport.remote_repo(['C'], 'http://repo_c.my-codes.example.com/python_packages'):
... withhttpimport.remote_repo(['B'], 'http://repo_b.my-codes.example.com/python_packages'):
... withhttpimport.remote_repo(['A'], 'http://repo_a.my-codes.example.com/python_packages'):
... importA
... # Asks for A, Searches for B, Asks for B, Searches for C, Asks for C --> Resolves --> Imports A>>>
>>>module_object=httpimport.load('package1', 'http://my-codes.example.com/python_packages')
>>>module_object<module'package1'from'http://my-codes.example.com/python_packages/package1/__init__.py'>

Example - The Whole Picture

Using the SimpleHTTPServer, a whole directory can be served through HTTP as follows:

user@hostname:/tmp/test_directory$ ls -R
.:
test_package
./test_package:
__init__.py __init__.pyc module1.py module2.py
user@hostname:/tmp/test_directory$
user@hostname:/tmp/test_directory$ python -m SimpleHTTPServer &
[1] 9565
Serving HTTP on 0.0.0.0 port 8000 ...
user@hostname:/tmp/test_directory$
user@hostname:/tmp/test_directory$
user@hostname:/tmp/test_directory$ curl http://localhost:8000/test_package/module1.py
127.0.0.1 - - [22/Aug/2017 17:42:49] "GET /test_package/module1.py HTTP/1.1" 200 -
def dummy_func() :return'Function Loaded'
class dummy_class :
def dummy_method(self) :return'Class and method loaded'
dummy_str = 'Constant Loaded'
user@hostname:/tmp/test_directory$
user@hostname:/tmp/test_directory$ curl http://localhost:8000/test_package/__init__.py
127.0.0.1 - - [22/Aug/2017 17:45:20] "GET /test_package/__init__.py HTTP/1.1" 200 -
__all__ = ["module1", "module2"]

Using this simple built-in feature of Py2/3, a custom importer can been created, that given a base URL and a list of package names, it fetches and automatically loads all modules and packages to the local namespace.

Usage

Making the HTTP repo

user@hostname:/tmp/test_directory$ ls -R
.:
test_package
./test_package:
__init__.py __init__.pyc module1.py module2.py
user@hostname:/tmp/test_directory$
user@hostname:/tmp/test_directory$ python -m SimpleHTTPServer
Serving HTTP on 0.0.0.0 port 8000 ...

Importing Remotely

add_remote_repo() and remove_remote_repo()

These 2 functions will add and remove to the default sys.meta_path custom HttpImporter objects, given the URL they will look for packages/modules and a list of packages/modules its one can serve.

>>>importtest_packageTraceback (mostrecentcalllast):
File"<stdin>", line1, in<module>ImportError: Nomodulenamedtest_package>>>>>>fromhttpimportimportadd_remote_repo, remove_remote_repo>>># In the given URL the 'test_package/' is available>>>add_remote_repo(['test_package'], 'http://localhost:8000/') # >>>importtest_package>>>>>>remove_remote_repo('http://localhost:8000/')
>>>importtest_package.module1Traceback (mostrecentcalllast):
File"<stdin>", line1, in<module>ImportError: Nomodulenamedmodule1

The remote_repo() context

Adding and removing Remote Repos can be a pain, specially if there are packages that are available in more than one repos. So the with keyword does the trick again:

>>>fromhttpimportimportremote_repo>>>>>>>>>withremote_repo(['test_package'], 'http://localhost:8000/') :
... fromtest_packageimportmodule1
...
>>>>>>fromtest_packageimportmodule2Traceback (mostrecentcalllast):
File"<stdin>", line1, in<module>ImportError: cannotimportnamemodule2>>>module1.dummy_str'Constant Loaded'>>>module1.dummy_func<functiondummy_funcat0x7f7a8a170410>

Reload module (setting httpimport.RELOAD flag):

importimportlibimporthttpimporthttpimport.INSECURE=Truewithhttpimport.remote_repo(['mod'], 'http://localhost:8000/a'):
importmodprint(mod.module_name())
withhttpimport.remote_repo(['mod'], 'http://localhost:8000/b'):
importlib.reload(mod)
importmodprint(mod.module_name())
httpimport.RELOAD=True# Allow reload modulewithhttpimport.remote_repo(['mod'], 'http://localhost:8000/b'):
importlib.reload(mod)
importmodprint(mod.module_name())

The Tiny Test for your amusement

The test.py file contains a minimal test. Try changing working directories and package names and see what happens...

$ python test.py
serving at port 8000
127.0.0.1 - - [22/Aug/2017 17:36:44] code 404, message File not found
127.0.0.1 - - [22/Aug/2017 17:36:44] "GET /test_package/module1/__init__.py HTTP/1.1" 404 -
127.0.0.1 - - [22/Aug/2017 17:36:44] "GET /test_package/module1.py HTTP/1.1" 200 -
Constant Loaded
Function Loaded
Class and method loaded

The Github Use Case!

Such HTTP Servers (serving Python packages in a directory structured way) can be found in the wild, not only created with SimpleHTTPServer. Github repos can serve as Python HTTPS Repos as well!!!

Here is an example with my beloved covertutils project:

>>>>>>importcovertutilsTraceback (mostrecentcalllast):
File"<stdin>", line1, in<module>ImportError: Nomodulenamedcovertutils>>># covertutils is not available through normal import!>>>>>>covertutils_url='https://raw.githubusercontent.com/operatorequals/covertutils/master/'>>>>>>fromhttpimportimportremote_repo>>>>>>withremote_repo(['covertutils'], covertutils_url) :
... importcovertutils
...
>>>printcovertutils.__author__JohnTorakis-operatorequals

The dedicatedgithub_repo() context:

>>>fromhttpimportimportgithub_repo>>>withgithub_repo( 'operatorequals', 'covertutils', ) :
... importcovertutils
...
>>>covertutils.__author__'John Torakis - operatorequals'>>>

What about branches?

>>>fromhttpimportimportgithub_repo>>>withgithub_repo( 'operatorequals', 'covertutils', branch='py3_compatibility' ) :
... importcovertutils
...
>>>covertutils.__author__'John Torakis - operatorequals'>>>

And ad-hoc commits too?

What if you need to stick to a fixed -known to work- commit?

>>>fromhttpimportimportgithub_repo>>>withgithub_repo( 'operatorequals', 'covertutils', commit='cf3f78c77c437edf2c291bd5b4ed27e0a93e6a77' ) :
... importcovertutils
...
>>>covertutils.__author__'John Torakis - operatorequals'>>>

The newer sibling bitbucket_repo() (as of 0.5.9)

>>>withbitbucket_repo('atlassian', 'python-bitbucket', module='pybitbucket'):
... importpybitbucket
...
>>>

Recursive Dependencies

If package A requires module B and A exists in http://example.com/a_repo/, while B exists in http://example.com/b_repo/, then A can be imported using the following technique:

>>>fromhttpimportimportremote_repo>>>withremote_repo(['B'],"http://example.com/b_repo/") :
... withremote_repo(['A'],"http://example.com/a_repo/") :
... importA
... [!] 'B'notfoundinHTTPrepository. MovingtonextFinder.
>>>>>>A<module'A'from'http://example.com/a_repo/A/__init__.py'>>>>B<module'B'from'http://example.com/a_repo/B.py'>>>>

Any combination of packages and modules can be imported this way!

The [!] Warning was emitted by the HttpImporter object created for A, as it couldn't locate B, and passed control to the next Finder object, that happened to be the HttpImporter object created for B!

The load() function (as of 0.5.10)

The load() function was added to make module loading possible without Namespace pollution.

>>>importhttpimport>>>pack1=httpimport.load('random-package','http://localhost:8000/')
>>>pack1<module'random-package'from'http://localhost:8000//random-package/__init__.py'>>>>>>># Trying to load 'os' module from the URL will fail, as it won't delegate to to other Finders/Loaders.>>>httpimport.load('os','http://localhost:8000/')
[!] 'non-existent-package'notfoundinHTTPrepository. MovingtonextFinder.
Traceback (mostrecentcalllast):
File"<stdin>", line1, in<module>File"httpimport.py", line287, inloadraiseImportError("Module '%s' cannot be imported from '%s'"% (module_name, url) )
ImportError: Module'os'cannotbeimportedfrom'http://localhost:8000/'

And no data touches the disk, nor any virtual environment. The import happens just to the running Python process!

Life suddenly got simpler for Python module testing!!!

Imagine the breeze of testing Pull Requests and packages that you aren't sure they will work for you!

Debugging...

>>>fromhttpimportimport*>>>>>>importlogging>>>logging.getLogger('httpimport').setLevel(logging.DEBUG)
>>>>>>withgithub_repo('operatorequals','covertutils') :
... importcovertutils
...
FINDER=================
[!] Searchingcovertutils
[!] PathisNone
[@] CheckingifconnectionisHTTPSsecure>
[@] Checkingifindeclaredremotemodulenames>
[@] Checkingifbuilt-in>
[@] Checkingifitisnamerepetition>
[*]Module/Package'covertutils'canbeloaded!
LOADER=================
[+] Loadingcovertutils
[+] Tryingtoimportaspackagefrom: 'https://raw.githubusercontent.com/operatorequals/covertutils/master//covertutils/__init__.py'
[+] Importing'covertutils'
[+] Readytoexecute'covertutils'code
[+] 'covertutils'importedsuccesfully!
>>>

Beware: Huge Security Implications!

Using the httpimport with HTTP URLs is highly discouraged outside the localhost interface!

As HTTP traffic isn't encrypted and/or integrity checked (unlike HTTPS), it is trivial for a remote attacker to intercept the HTTP responses (via an ARP MiTM probably), and add arbitrary Python code to the downloaded packages/modules. This will directly result in Remote Code Execution to your current user's context! In other words, you get totally F*ed...

Preventing the disaster (setting httpimport.INSECURE flag):

>>>importhttpimport>>>>>># Importing from plain HTTP ...>>>httpimport.load('test_module', 'http://localhost:8000//')
[!] UsingnonHTTPSURLs ('http://localhost:8000//') canbeasecurityhazard!
[-] 'httpimport.INSECUREisnotset! Aborting...
Traceback (mostrecentcalllast):
File"<stdin>", line1, in<module>File"httpimport.py", line302, inloadraiseImportError("Module '%s' cannot be imported from URL: '%s'"% (module_name, url) )
ImportError: Module'test_module'cannotbeimportedfromURL: 'http://localhost:8000/'>>># ... Throws Error!>>>>>># Importing from plain HTTP has to be DELIBERATELY enabled!>>>httpimport.INSECURE=True>>>httpimport.load('test_module', 'http://localhost:8000//')
[!] UsingnonHTTPSURLs ('http://localhost:8000//') canbeasecurityhazard!
<module'test_module'from'http://localhost:8000//test_module.py'>>>># Succeeded!

You have been warned! Use HTTPS URLs with httpimport!

Did I hear you say "Staging protocol for covertutils backdoors"?

Technique documentation on using httpimport to stage covertutils backdoor code, making EXE packed and unreadable code load non-included module dependencies.

About

Module for remote in-memory Python package/module loading through HTTP/S

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

httpimport

Python's missing feature!

Remote, in-memory Python package/moduleimporting through HTTP/S

PyPI version

A feature that Python2/3misses and has become popular in other languages is the remote loading of packages/modules.

httpimport lets a Python2/3 packages/modules to be imported directly in Python interpreter's process memory, through remote URIs, and more...

Example - In a Nutshell

>>>importhttpimport>>>httpimport.__all__
['HttpImporter', 'add_remote_repo', 'remove_remote_repo', 'remote_repo', 'github_repo', 'bitbucket_repo']
>>>withhttpimport.remote_repo(['package1','package2','package3'], 'http://my-codes.example.com/python_packages'):
... importpackage1
...
>>>withhttpimport.github_repo('operatorequals', 'covertutils', branch=master):
... importcovertutils
... # Also works with 'bitbucket_repo'
>>># A depends to B and B depends to C (A, B, C : Python modules/packages in different domains):>>># A exists in "repo_a.my-codes.example.com"	|>>># B exists in "repo_b.my-codes.example.com" | <-- Different domains>>># C exists in "repo_c.my-codes.example.com" |>>>withhttpimport.remote_repo(['C'], 'http://repo_c.my-codes.example.com/python_packages'):
... withhttpimport.remote_repo(['B'], 'http://repo_b.my-codes.example.com/python_packages'):
... withhttpimport.remote_repo(['A'], 'http://repo_a.my-codes.example.com/python_packages'):
... importA
... # Asks for A, Searches for B, Asks for B, Searches for C, Asks for C --> Resolves --> Imports A>>>
>>>module_object=httpimport.load('package1', 'http://my-codes.example.com/python_packages')
>>>module_object<module'package1'from'http://my-codes.example.com/python_packages/package1/__init__.py'>

Example - The Whole Picture

Using the SimpleHTTPServer, a whole directory can be served through HTTP as follows:

user@hostname:/tmp/test_directory$ ls -R
.:
test_package
./test_package:
__init__.py __init__.pyc module1.py module2.py
user@hostname:/tmp/test_directory$
user@hostname:/tmp/test_directory$ python -m SimpleHTTPServer &
[1] 9565
Serving HTTP on 0.0.0.0 port 8000 ...
user@hostname:/tmp/test_directory$
user@hostname:/tmp/test_directory$
user@hostname:/tmp/test_directory$ curl http://localhost:8000/test_package/module1.py
127.0.0.1 - - [22/Aug/2017 17:42:49] "GET /test_package/module1.py HTTP/1.1" 200 -
def dummy_func() :return'Function Loaded'
class dummy_class :
def dummy_method(self) :return'Class and method loaded'
dummy_str = 'Constant Loaded'
user@hostname:/tmp/test_directory$
user@hostname:/tmp/test_directory$ curl http://localhost:8000/test_package/__init__.py
127.0.0.1 - - [22/Aug/2017 17:45:20] "GET /test_package/__init__.py HTTP/1.1" 200 -
__all__ = ["module1", "module2"]

Using this simple built-in feature of Py2/3, a custom importer can been created, that given a base URL and a list of package names, it fetches and automatically loads all modules and packages to the local namespace.

Usage

Making the HTTP repo

user@hostname:/tmp/test_directory$ ls -R
.:
test_package
./test_package:
__init__.py __init__.pyc module1.py module2.py
user@hostname:/tmp/test_directory$
user@hostname:/tmp/test_directory$ python -m SimpleHTTPServer
Serving HTTP on 0.0.0.0 port 8000 ...

Importing Remotely

add_remote_repo() and remove_remote_repo()

These 2 functions will add and remove to the default sys.meta_path custom HttpImporter objects, given the URL they will look for packages/modules and a list of packages/modules its one can serve.

>>>importtest_packageTraceback (mostrecentcalllast):
File"<stdin>", line1, in<module>ImportError: Nomodulenamedtest_package>>>>>>fromhttpimportimportadd_remote_repo, remove_remote_repo>>># In the given URL the 'test_package/' is available>>>add_remote_repo(['test_package'], 'http://localhost:8000/') # >>>importtest_package>>>>>>remove_remote_repo('http://localhost:8000/')
>>>importtest_package.module1Traceback (mostrecentcalllast):
File"<stdin>", line1, in<module>ImportError: Nomodulenamedmodule1

The remote_repo() context

Adding and removing Remote Repos can be a pain, specially if there are packages that are available in more than one repos. So the with keyword does the trick again:

>>>fromhttpimportimportremote_repo>>>>>>>>>withremote_repo(['test_package'], 'http://localhost:8000/') :
... fromtest_packageimportmodule1
...
>>>>>>fromtest_packageimportmodule2Traceback (mostrecentcalllast):
File"<stdin>", line1, in<module>ImportError: cannotimportnamemodule2>>>module1.dummy_str'Constant Loaded'>>>module1.dummy_func<functiondummy_funcat0x7f7a8a170410>

Reload module (setting httpimport.RELOAD flag):

importimportlibimporthttpimporthttpimport.INSECURE=Truewithhttpimport.remote_repo(['mod'], 'http://localhost:8000/a'):
importmodprint(mod.module_name())
withhttpimport.remote_repo(['mod'], 'http://localhost:8000/b'):
importlib.reload(mod)
importmodprint(mod.module_name())
httpimport.RELOAD=True# Allow reload modulewithhttpimport.remote_repo(['mod'], 'http://localhost:8000/b'):
importlib.reload(mod)
importmodprint(mod.module_name())

The Tiny Test for your amusement

The test.py file contains a minimal test. Try changing working directories and package names and see what happens...

$ python test.py
serving at port 8000
127.0.0.1 - - [22/Aug/2017 17:36:44] code 404, message File not found
127.0.0.1 - - [22/Aug/2017 17:36:44] "GET /test_package/module1/__init__.py HTTP/1.1" 404 -
127.0.0.1 - - [22/Aug/2017 17:36:44] "GET /test_package/module1.py HTTP/1.1" 200 -
Constant Loaded
Function Loaded
Class and method loaded

The Github Use Case!

Such HTTP Servers (serving Python packages in a directory structured way) can be found in the wild, not only created with SimpleHTTPServer. Github repos can serve as Python HTTPS Repos as well!!!

Here is an example with my beloved covertutils project:

>>>>>>importcovertutilsTraceback (mostrecentcalllast):
File"<stdin>", line1, in<module>ImportError: Nomodulenamedcovertutils>>># covertutils is not available through normal import!>>>>>>covertutils_url='https://raw.githubusercontent.com/operatorequals/covertutils/master/'>>>>>>fromhttpimportimportremote_repo>>>>>>withremote_repo(['covertutils'], covertutils_url) :
... importcovertutils
...
>>>printcovertutils.__author__JohnTorakis-operatorequals

The dedicatedgithub_repo() context:

>>>fromhttpimportimportgithub_repo>>>withgithub_repo( 'operatorequals', 'covertutils', ) :
... importcovertutils
...
>>>covertutils.__author__'John Torakis - operatorequals'>>>

What about branches?

>>>fromhttpimportimportgithub_repo>>>withgithub_repo( 'operatorequals', 'covertutils', branch='py3_compatibility' ) :
... importcovertutils
...
>>>covertutils.__author__'John Torakis - operatorequals'>>>

And ad-hoc commits too?

What if you need to stick to a fixed -known to work- commit?

>>>fromhttpimportimportgithub_repo>>>withgithub_repo( 'operatorequals', 'covertutils', commit='cf3f78c77c437edf2c291bd5b4ed27e0a93e6a77' ) :
... importcovertutils
...
>>>covertutils.__author__'John Torakis - operatorequals'>>>

The newer sibling bitbucket_repo() (as of 0.5.9)

>>>withbitbucket_repo('atlassian', 'python-bitbucket', module='pybitbucket'):
... importpybitbucket
...
>>>

Recursive Dependencies

If package A requires module B and A exists in http://example.com/a_repo/, while B exists in http://example.com/b_repo/, then A can be imported using the following technique:

>>>fromhttpimportimportremote_repo>>>withremote_repo(['B'],"http://example.com/b_repo/") :
... withremote_repo(['A'],"http://example.com/a_repo/") :
... importA
... [!] 'B'notfoundinHTTPrepository. MovingtonextFinder.
>>>>>>A<module'A'from'http://example.com/a_repo/A/__init__.py'>>>>B<module'B'from'http://example.com/a_repo/B.py'>>>>

Any combination of packages and modules can be imported this way!

The [!] Warning was emitted by the HttpImporter object created for A, as it couldn't locate B, and passed control to the next Finder object, that happened to be the HttpImporter object created for B!

The load() function (as of 0.5.10)

The load() function was added to make module loading possible without Namespace pollution.

>>>importhttpimport>>>pack1=httpimport.load('random-package','http://localhost:8000/')
>>>pack1<module'random-package'from'http://localhost:8000//random-package/__init__.py'>>>>>>># Trying to load 'os' module from the URL will fail, as it won't delegate to to other Finders/Loaders.>>>httpimport.load('os','http://localhost:8000/')
[!] 'non-existent-package'notfoundinHTTPrepository. MovingtonextFinder.
Traceback (mostrecentcalllast):
File"<stdin>", line1, in<module>File"httpimport.py", line287, inloadraiseImportError("Module '%s' cannot be imported from '%s'"% (module_name, url) )
ImportError: Module'os'cannotbeimportedfrom'http://localhost:8000/'

And no data touches the disk, nor any virtual environment. The import happens just to the running Python process!

Life suddenly got simpler for Python module testing!!!

Imagine the breeze of testing Pull Requests and packages that you aren't sure they will work for you!

Debugging...

>>>fromhttpimportimport*>>>>>>importlogging>>>logging.getLogger('httpimport').setLevel(logging.DEBUG)
>>>>>>withgithub_repo('operatorequals','covertutils') :
... importcovertutils
...
FINDER=================
[!] Searchingcovertutils
[!] PathisNone
[@] CheckingifconnectionisHTTPSsecure>
[@] Checkingifindeclaredremotemodulenames>
[@] Checkingifbuilt-in>
[@] Checkingifitisnamerepetition>
[*]Module/Package'covertutils'canbeloaded!
LOADER=================
[+] Loadingcovertutils
[+] Tryingtoimportaspackagefrom: 'https://raw.githubusercontent.com/operatorequals/covertutils/master//covertutils/__init__.py'
[+] Importing'covertutils'
[+] Readytoexecute'covertutils'code
[+] 'covertutils'importedsuccesfully!
>>>

Beware: Huge Security Implications!

Using the httpimport with HTTP URLs is highly discouraged outside the localhost interface!

As HTTP traffic isn't encrypted and/or integrity checked (unlike HTTPS), it is trivial for a remote attacker to intercept the HTTP responses (via an ARP MiTM probably), and add arbitrary Python code to the downloaded packages/modules. This will directly result in Remote Code Execution to your current user's context! In other words, you get totally F*ed...

Preventing the disaster (setting httpimport.INSECURE flag):

>>>importhttpimport>>>>>># Importing from plain HTTP ...>>>httpimport.load('test_module', 'http://localhost:8000//')
[!] UsingnonHTTPSURLs ('http://localhost:8000//') canbeasecurityhazard!
[-] 'httpimport.INSECUREisnotset! Aborting...
Traceback (mostrecentcalllast):
File"<stdin>", line1, in<module>File"httpimport.py", line302, inloadraiseImportError("Module '%s' cannot be imported from URL: '%s'"% (module_name, url) )
ImportError: Module'test_module'cannotbeimportedfromURL: 'http://localhost:8000/'>>># ... Throws Error!>>>>>># Importing from plain HTTP has to be DELIBERATELY enabled!>>>httpimport.INSECURE=True>>>httpimport.load('test_module', 'http://localhost:8000//')
[!] UsingnonHTTPSURLs ('http://localhost:8000//') canbeasecurityhazard!
<module'test_module'from'http://localhost:8000//test_module.py'>>>># Succeeded!

You have been warned! Use HTTPS URLs with httpimport!

Did I hear you say "Staging protocol for covertutils backdoors"?

Technique documentation on using httpimport to stage covertutils backdoor code, making EXE packed and unreadable code load non-included module dependencies.

About

Module for remote in-memory Python package/module loading through HTTP/S

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

httpimport

Python's missing feature!

Remote, in-memory Python package/moduleimporting through HTTP/S

PyPI version

A feature that Python2/3misses and has become popular in other languages is the remote loading of packages/modules.

httpimport lets a Python2/3 packages/modules to be imported directly in Python interpreter's process memory, through remote URIs, and more...

Example - In a Nutshell

>>>importhttpimport>>>httpimport.__all__
['HttpImporter', 'add_remote_repo', 'remove_remote_repo', 'remote_repo', 'github_repo', 'bitbucket_repo']
>>>withhttpimport.remote_repo(['package1','package2','package3'], 'http://my-codes.example.com/python_packages'):
... importpackage1
...
>>>withhttpimport.github_repo('operatorequals', 'covertutils', branch=master):
... importcovertutils
... # Also works with 'bitbucket_repo'
>>># A depends to B and B depends to C (A, B, C : Python modules/packages in different domains):>>># A exists in "repo_a.my-codes.example.com"	|>>># B exists in "repo_b.my-codes.example.com" | <-- Different domains>>># C exists in "repo_c.my-codes.example.com" |>>>withhttpimport.remote_repo(['C'], 'http://repo_c.my-codes.example.com/python_packages'):
... withhttpimport.remote_repo(['B'], 'http://repo_b.my-codes.example.com/python_packages'):
... withhttpimport.remote_repo(['A'], 'http://repo_a.my-codes.example.com/python_packages'):
... importA
... # Asks for A, Searches for B, Asks for B, Searches for C, Asks for C --> Resolves --> Imports A>>>
>>>module_object=httpimport.load('package1', 'http://my-codes.example.com/python_packages')
>>>module_object<module'package1'from'http://my-codes.example.com/python_packages/package1/__init__.py'>

Example - The Whole Picture

Using the SimpleHTTPServer, a whole directory can be served through HTTP as follows:

user@hostname:/tmp/test_directory$ ls -R
.:
test_package
./test_package:
__init__.py __init__.pyc module1.py module2.py
user@hostname:/tmp/test_directory$
user@hostname:/tmp/test_directory$ python -m SimpleHTTPServer &
[1] 9565
Serving HTTP on 0.0.0.0 port 8000 ...
user@hostname:/tmp/test_directory$
user@hostname:/tmp/test_directory$
user@hostname:/tmp/test_directory$ curl http://localhost:8000/test_package/module1.py
127.0.0.1 - - [22/Aug/2017 17:42:49] "GET /test_package/module1.py HTTP/1.1" 200 -
def dummy_func() :return'Function Loaded'
class dummy_class :
def dummy_method(self) :return'Class and method loaded'
dummy_str = 'Constant Loaded'
user@hostname:/tmp/test_directory$
user@hostname:/tmp/test_directory$ curl http://localhost:8000/test_package/__init__.py
127.0.0.1 - - [22/Aug/2017 17:45:20] "GET /test_package/__init__.py HTTP/1.1" 200 -
__all__ = ["module1", "module2"]

Using this simple built-in feature of Py2/3, a custom importer can been created, that given a base URL and a list of package names, it fetches and automatically loads all modules and packages to the local namespace.

Usage

Making the HTTP repo

user@hostname:/tmp/test_directory$ ls -R
.:
test_package
./test_package:
__init__.py __init__.pyc module1.py module2.py
user@hostname:/tmp/test_directory$
user@hostname:/tmp/test_directory$ python -m SimpleHTTPServer
Serving HTTP on 0.0.0.0 port 8000 ...

Importing Remotely

add_remote_repo() and remove_remote_repo()

These 2 functions will add and remove to the default sys.meta_path custom HttpImporter objects, given the URL they will look for packages/modules and a list of packages/modules its one can serve.

>>>importtest_packageTraceback (mostrecentcalllast):
File"<stdin>", line1, in<module>ImportError: Nomodulenamedtest_package>>>>>>fromhttpimportimportadd_remote_repo, remove_remote_repo>>># In the given URL the 'test_package/' is available>>>add_remote_repo(['test_package'], 'http://localhost:8000/') # >>>importtest_package>>>>>>remove_remote_repo('http://localhost:8000/')
>>>importtest_package.module1Traceback (mostrecentcalllast):
File"<stdin>", line1, in<module>ImportError: Nomodulenamedmodule1

The remote_repo() context

Adding and removing Remote Repos can be a pain, specially if there are packages that are available in more than one repos. So the with keyword does the trick again:

>>>fromhttpimportimportremote_repo>>>>>>>>>withremote_repo(['test_package'], 'http://localhost:8000/') :
... fromtest_packageimportmodule1
...
>>>>>>fromtest_packageimportmodule2Traceback (mostrecentcalllast):
File"<stdin>", line1, in<module>ImportError: cannotimportnamemodule2>>>module1.dummy_str'Constant Loaded'>>>module1.dummy_func<functiondummy_funcat0x7f7a8a170410>

Reload module (setting httpimport.RELOAD flag):

importimportlibimporthttpimporthttpimport.INSECURE=Truewithhttpimport.remote_repo(['mod'], 'http://localhost:8000/a'):
importmodprint(mod.module_name())
withhttpimport.remote_repo(['mod'], 'http://localhost:8000/b'):
importlib.reload(mod)
importmodprint(mod.module_name())
httpimport.RELOAD=True# Allow reload modulewithhttpimport.remote_repo(['mod'], 'http://localhost:8000/b'):
importlib.reload(mod)
importmodprint(mod.module_name())

The Tiny Test for your amusement

The test.py file contains a minimal test. Try changing working directories and package names and see what happens...

$ python test.py
serving at port 8000
127.0.0.1 - - [22/Aug/2017 17:36:44] code 404, message File not found
127.0.0.1 - - [22/Aug/2017 17:36:44] "GET /test_package/module1/__init__.py HTTP/1.1" 404 -
127.0.0.1 - - [22/Aug/2017 17:36:44] "GET /test_package/module1.py HTTP/1.1" 200 -
Constant Loaded
Function Loaded
Class and method loaded

The Github Use Case!

Such HTTP Servers (serving Python packages in a directory structured way) can be found in the wild, not only created with SimpleHTTPServer. Github repos can serve as Python HTTPS Repos as well!!!

Here is an example with my beloved covertutils project:

>>>>>>importcovertutilsTraceback (mostrecentcalllast):
File"<stdin>", line1, in<module>ImportError: Nomodulenamedcovertutils>>># covertutils is not available through normal import!>>>>>>covertutils_url='https://raw.githubusercontent.com/operatorequals/covertutils/master/'>>>>>>fromhttpimportimportremote_repo>>>>>>withremote_repo(['covertutils'], covertutils_url) :
... importcovertutils
...
>>>printcovertutils.__author__JohnTorakis-operatorequals

The dedicatedgithub_repo() context:

>>>fromhttpimportimportgithub_repo>>>withgithub_repo( 'operatorequals', 'covertutils', ) :
... importcovertutils
...
>>>covertutils.__author__'John Torakis - operatorequals'>>>

What about branches?

>>>fromhttpimportimportgithub_repo>>>withgithub_repo( 'operatorequals', 'covertutils', branch='py3_compatibility' ) :
... importcovertutils
...
>>>covertutils.__author__'John Torakis - operatorequals'>>>

And ad-hoc commits too?

What if you need to stick to a fixed -known to work- commit?

>>>fromhttpimportimportgithub_repo>>>withgithub_repo( 'operatorequals', 'covertutils', commit='cf3f78c77c437edf2c291bd5b4ed27e0a93e6a77' ) :
... importcovertutils
...
>>>covertutils.__author__'John Torakis - operatorequals'>>>

The newer sibling bitbucket_repo() (as of 0.5.9)

>>>withbitbucket_repo('atlassian', 'python-bitbucket', module='pybitbucket'):
... importpybitbucket
...
>>>

Recursive Dependencies

If package A requires module B and A exists in http://example.com/a_repo/, while B exists in http://example.com/b_repo/, then A can be imported using the following technique:

>>>fromhttpimportimportremote_repo>>>withremote_repo(['B'],"http://example.com/b_repo/") :
... withremote_repo(['A'],"http://example.com/a_repo/") :
... importA
... [!] 'B'notfoundinHTTPrepository. MovingtonextFinder.
>>>>>>A<module'A'from'http://example.com/a_repo/A/__init__.py'>>>>B<module'B'from'http://example.com/a_repo/B.py'>>>>

Any combination of packages and modules can be imported this way!

The [!] Warning was emitted by the HttpImporter object created for A, as it couldn't locate B, and passed control to the next Finder object, that happened to be the HttpImporter object created for B!

The load() function (as of 0.5.10)

The load() function was added to make module loading possible without Namespace pollution.

>>>importhttpimport>>>pack1=httpimport.load('random-package','http://localhost:8000/')
>>>pack1<module'random-package'from'http://localhost:8000//random-package/__init__.py'>>>>>>># Trying to load 'os' module from the URL will fail, as it won't delegate to to other Finders/Loaders.>>>httpimport.load('os','http://localhost:8000/')
[!] 'non-existent-package'notfoundinHTTPrepository. MovingtonextFinder.
Traceback (mostrecentcalllast):
File"<stdin>", line1, in<module>File"httpimport.py", line287, inloadraiseImportError("Module '%s' cannot be imported from '%s'"% (module_name, url) )
ImportError: Module'os'cannotbeimportedfrom'http://localhost:8000/'

And no data touches the disk, nor any virtual environment. The import happens just to the running Python process!

Life suddenly got simpler for Python module testing!!!

Imagine the breeze of testing Pull Requests and packages that you aren't sure they will work for you!

Debugging...

>>>fromhttpimportimport*>>>>>>importlogging>>>logging.getLogger('httpimport').setLevel(logging.DEBUG)
>>>>>>withgithub_repo('operatorequals','covertutils') :
... importcovertutils
...
FINDER=================
[!] Searchingcovertutils
[!] PathisNone
[@] CheckingifconnectionisHTTPSsecure>
[@] Checkingifindeclaredremotemodulenames>
[@] Checkingifbuilt-in>
[@] Checkingifitisnamerepetition>
[*]Module/Package'covertutils'canbeloaded!
LOADER=================
[+] Loadingcovertutils
[+] Tryingtoimportaspackagefrom: 'https://raw.githubusercontent.com/operatorequals/covertutils/master//covertutils/__init__.py'
[+] Importing'covertutils'
[+] Readytoexecute'covertutils'code
[+] 'covertutils'importedsuccesfully!
>>>

Beware: Huge Security Implications!

Using the httpimport with HTTP URLs is highly discouraged outside the localhost interface!

As HTTP traffic isn't encrypted and/or integrity checked (unlike HTTPS), it is trivial for a remote attacker to intercept the HTTP responses (via an ARP MiTM probably), and add arbitrary Python code to the downloaded packages/modules. This will directly result in Remote Code Execution to your current user's context! In other words, you get totally F*ed...

Preventing the disaster (setting httpimport.INSECURE flag):

>>>importhttpimport>>>>>># Importing from plain HTTP ...>>>httpimport.load('test_module', 'http://localhost:8000//')
[!] UsingnonHTTPSURLs ('http://localhost:8000//') canbeasecurityhazard!
[-] 'httpimport.INSECUREisnotset! Aborting...
Traceback (mostrecentcalllast):
File"<stdin>", line1, in<module>File"httpimport.py", line302, inloadraiseImportError("Module '%s' cannot be imported from URL: '%s'"% (module_name, url) )
ImportError: Module'test_module'cannotbeimportedfromURL: 'http://localhost:8000/'>>># ... Throws Error!>>>>>># Importing from plain HTTP has to be DELIBERATELY enabled!>>>httpimport.INSECURE=True>>>httpimport.load('test_module', 'http://localhost:8000//')
[!] UsingnonHTTPSURLs ('http://localhost:8000//') canbeasecurityhazard!
<module'test_module'from'http://localhost:8000//test_module.py'>>>># Succeeded!

You have been warned! Use HTTPS URLs with httpimport!

Did I hear you say "Staging protocol for covertutils backdoors"?

Technique documentation on using httpimport to stage covertutils backdoor code, making EXE packed and unreadable code load non-included module dependencies.

About

Module for remote in-memory Python package/module loading through HTTP/S

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

httpimport

Python's missing feature!

Remote, in-memory Python package/moduleimporting through HTTP/S

PyPI version

A feature that Python2/3misses and has become popular in other languages is the remote loading of packages/modules.

httpimport lets a Python2/3 packages/modules to be imported directly in Python interpreter's process memory, through remote URIs, and more...

Example - In a Nutshell

>>>importhttpimport>>>httpimport.__all__
['HttpImporter', 'add_remote_repo', 'remove_remote_repo', 'remote_repo', 'github_repo', 'bitbucket_repo']
>>>withhttpimport.remote_repo(['package1','package2','package3'], 'http://my-codes.example.com/python_packages'):
... importpackage1
...
>>>withhttpimport.github_repo('operatorequals', 'covertutils', branch=master):
... importcovertutils
... # Also works with 'bitbucket_repo'
>>># A depends to B and B depends to C (A, B, C : Python modules/packages in different domains):>>># A exists in "repo_a.my-codes.example.com"	|>>># B exists in "repo_b.my-codes.example.com" | <-- Different domains>>># C exists in "repo_c.my-codes.example.com" |>>>withhttpimport.remote_repo(['C'], 'http://repo_c.my-codes.example.com/python_packages'):
... withhttpimport.remote_repo(['B'], 'http://repo_b.my-codes.example.com/python_packages'):
... withhttpimport.remote_repo(['A'], 'http://repo_a.my-codes.example.com/python_packages'):
... importA
... # Asks for A, Searches for B, Asks for B, Searches for C, Asks for C --> Resolves --> Imports A>>>
>>>module_object=httpimport.load('package1', 'http://my-codes.example.com/python_packages')
>>>module_object<module'package1'from'http://my-codes.example.com/python_packages/package1/__init__.py'>

Example - The Whole Picture

Using the SimpleHTTPServer, a whole directory can be served through HTTP as follows:

user@hostname:/tmp/test_directory$ ls -R
.:
test_package
./test_package:
__init__.py __init__.pyc module1.py module2.py
user@hostname:/tmp/test_directory$
user@hostname:/tmp/test_directory$ python -m SimpleHTTPServer &
[1] 9565
Serving HTTP on 0.0.0.0 port 8000 ...
user@hostname:/tmp/test_directory$
user@hostname:/tmp/test_directory$
user@hostname:/tmp/test_directory$ curl http://localhost:8000/test_package/module1.py
127.0.0.1 - - [22/Aug/2017 17:42:49] "GET /test_package/module1.py HTTP/1.1" 200 -
def dummy_func() :return'Function Loaded'
class dummy_class :
def dummy_method(self) :return'Class and method loaded'
dummy_str = 'Constant Loaded'
user@hostname:/tmp/test_directory$
user@hostname:/tmp/test_directory$ curl http://localhost:8000/test_package/__init__.py
127.0.0.1 - - [22/Aug/2017 17:45:20] "GET /test_package/__init__.py HTTP/1.1" 200 -
__all__ = ["module1", "module2"]

Using this simple built-in feature of Py2/3, a custom importer can been created, that given a base URL and a list of package names, it fetches and automatically loads all modules and packages to the local namespace.

Usage

Making the HTTP repo

user@hostname:/tmp/test_directory$ ls -R
.:
test_package
./test_package:
__init__.py __init__.pyc module1.py module2.py
user@hostname:/tmp/test_directory$
user@hostname:/tmp/test_directory$ python -m SimpleHTTPServer
Serving HTTP on 0.0.0.0 port 8000 ...

Importing Remotely

add_remote_repo() and remove_remote_repo()

These 2 functions will add and remove to the default sys.meta_path custom HttpImporter objects, given the URL they will look for packages/modules and a list of packages/modules its one can serve.

>>>importtest_packageTraceback (mostrecentcalllast):
File"<stdin>", line1, in<module>ImportError: Nomodulenamedtest_package>>>>>>fromhttpimportimportadd_remote_repo, remove_remote_repo>>># In the given URL the 'test_package/' is available>>>add_remote_repo(['test_package'], 'http://localhost:8000/') # >>>importtest_package>>>>>>remove_remote_repo('http://localhost:8000/')
>>>importtest_package.module1Traceback (mostrecentcalllast):
File"<stdin>", line1, in<module>ImportError: Nomodulenamedmodule1

The remote_repo() context

Adding and removing Remote Repos can be a pain, specially if there are packages that are available in more than one repos. So the with keyword does the trick again:

>>>fromhttpimportimportremote_repo>>>>>>>>>withremote_repo(['test_package'], 'http://localhost:8000/') :
... fromtest_packageimportmodule1
...
>>>>>>fromtest_packageimportmodule2Traceback (mostrecentcalllast):
File"<stdin>", line1, in<module>ImportError: cannotimportnamemodule2>>>module1.dummy_str'Constant Loaded'>>>module1.dummy_func<functiondummy_funcat0x7f7a8a170410>

Reload module (setting httpimport.RELOAD flag):

importimportlibimporthttpimporthttpimport.INSECURE=Truewithhttpimport.remote_repo(['mod'], 'http://localhost:8000/a'):
importmodprint(mod.module_name())
withhttpimport.remote_repo(['mod'], 'http://localhost:8000/b'):
importlib.reload(mod)
importmodprint(mod.module_name())
httpimport.RELOAD=True# Allow reload modulewithhttpimport.remote_repo(['mod'], 'http://localhost:8000/b'):
importlib.reload(mod)
importmodprint(mod.module_name())

The Tiny Test for your amusement

The test.py file contains a minimal test. Try changing working directories and package names and see what happens...

$ python test.py
serving at port 8000
127.0.0.1 - - [22/Aug/2017 17:36:44] code 404, message File not found
127.0.0.1 - - [22/Aug/2017 17:36:44] "GET /test_package/module1/__init__.py HTTP/1.1" 404 -
127.0.0.1 - - [22/Aug/2017 17:36:44] "GET /test_package/module1.py HTTP/1.1" 200 -
Constant Loaded
Function Loaded
Class and method loaded

The Github Use Case!

Such HTTP Servers (serving Python packages in a directory structured way) can be found in the wild, not only created with SimpleHTTPServer. Github repos can serve as Python HTTPS Repos as well!!!

Here is an example with my beloved covertutils project:

>>>>>>importcovertutilsTraceback (mostrecentcalllast):
File"<stdin>", line1, in<module>ImportError: Nomodulenamedcovertutils>>># covertutils is not available through normal import!>>>>>>covertutils_url='https://raw.githubusercontent.com/operatorequals/covertutils/master/'>>>>>>fromhttpimportimportremote_repo>>>>>>withremote_repo(['covertutils'], covertutils_url) :
... importcovertutils
...
>>>printcovertutils.__author__JohnTorakis-operatorequals

The dedicatedgithub_repo() context:

>>>fromhttpimportimportgithub_repo>>>withgithub_repo( 'operatorequals', 'covertutils', ) :
... importcovertutils
...
>>>covertutils.__author__'John Torakis - operatorequals'>>>

What about branches?

>>>fromhttpimportimportgithub_repo>>>withgithub_repo( 'operatorequals', 'covertutils', branch='py3_compatibility' ) :
... importcovertutils
...
>>>covertutils.__author__'John Torakis - operatorequals'>>>

And ad-hoc commits too?

What if you need to stick to a fixed -known to work- commit?

>>>fromhttpimportimportgithub_repo>>>withgithub_repo( 'operatorequals', 'covertutils', commit='cf3f78c77c437edf2c291bd5b4ed27e0a93e6a77' ) :
... importcovertutils
...
>>>covertutils.__author__'John Torakis - operatorequals'>>>

The newer sibling bitbucket_repo() (as of 0.5.9)

>>>withbitbucket_repo('atlassian', 'python-bitbucket', module='pybitbucket'):
... importpybitbucket
...
>>>

Recursive Dependencies

If package A requires module B and A exists in http://example.com/a_repo/, while B exists in http://example.com/b_repo/, then A can be imported using the following technique:

>>>fromhttpimportimportremote_repo>>>withremote_repo(['B'],"http://example.com/b_repo/") :
... withremote_repo(['A'],"http://example.com/a_repo/") :
... importA
... [!] 'B'notfoundinHTTPrepository. MovingtonextFinder.
>>>>>>A<module'A'from'http://example.com/a_repo/A/__init__.py'>>>>B<module'B'from'http://example.com/a_repo/B.py'>>>>

Any combination of packages and modules can be imported this way!

The [!] Warning was emitted by the HttpImporter object created for A, as it couldn't locate B, and passed control to the next Finder object, that happened to be the HttpImporter object created for B!

The load() function (as of 0.5.10)

The load() function was added to make module loading possible without Namespace pollution.

>>>importhttpimport>>>pack1=httpimport.load('random-package','http://localhost:8000/')
>>>pack1<module'random-package'from'http://localhost:8000//random-package/__init__.py'>>>>>>># Trying to load 'os' module from the URL will fail, as it won't delegate to to other Finders/Loaders.>>>httpimport.load('os','http://localhost:8000/')
[!] 'non-existent-package'notfoundinHTTPrepository. MovingtonextFinder.
Traceback (mostrecentcalllast):
File"<stdin>", line1, in<module>File"httpimport.py", line287, inloadraiseImportError("Module '%s' cannot be imported from '%s'"% (module_name, url) )
ImportError: Module'os'cannotbeimportedfrom'http://localhost:8000/'

And no data touches the disk, nor any virtual environment. The import happens just to the running Python process!

Life suddenly got simpler for Python module testing!!!

Imagine the breeze of testing Pull Requests and packages that you aren't sure they will work for you!

Debugging...

>>>fromhttpimportimport*>>>>>>importlogging>>>logging.getLogger('httpimport').setLevel(logging.DEBUG)
>>>>>>withgithub_repo('operatorequals','covertutils') :
... importcovertutils
...
FINDER=================
[!] Searchingcovertutils
[!] PathisNone
[@] CheckingifconnectionisHTTPSsecure>
[@] Checkingifindeclaredremotemodulenames>
[@] Checkingifbuilt-in>
[@] Checkingifitisnamerepetition>
[*]Module/Package'covertutils'canbeloaded!
LOADER=================
[+] Loadingcovertutils
[+] Tryingtoimportaspackagefrom: 'https://raw.githubusercontent.com/operatorequals/covertutils/master//covertutils/__init__.py'
[+] Importing'covertutils'
[+] Readytoexecute'covertutils'code
[+] 'covertutils'importedsuccesfully!
>>>

Beware: Huge Security Implications!

Using the httpimport with HTTP URLs is highly discouraged outside the localhost interface!

As HTTP traffic isn't encrypted and/or integrity checked (unlike HTTPS), it is trivial for a remote attacker to intercept the HTTP responses (via an ARP MiTM probably), and add arbitrary Python code to the downloaded packages/modules. This will directly result in Remote Code Execution to your current user's context! In other words, you get totally F*ed...

Preventing the disaster (setting httpimport.INSECURE flag):

>>>importhttpimport>>>>>># Importing from plain HTTP ...>>>httpimport.load('test_module', 'http://localhost:8000//')
[!] UsingnonHTTPSURLs ('http://localhost:8000//') canbeasecurityhazard!
[-] 'httpimport.INSECUREisnotset! Aborting...
Traceback (mostrecentcalllast):
File"<stdin>", line1, in<module>File"httpimport.py", line302, inloadraiseImportError("Module '%s' cannot be imported from URL: '%s'"% (module_name, url) )
ImportError: Module'test_module'cannotbeimportedfromURL: 'http://localhost:8000/'>>># ... Throws Error!>>>>>># Importing from plain HTTP has to be DELIBERATELY enabled!>>>httpimport.INSECURE=True>>>httpimport.load('test_module', 'http://localhost:8000//')
[!] UsingnonHTTPSURLs ('http://localhost:8000//') canbeasecurityhazard!
<module'test_module'from'http://localhost:8000//test_module.py'>>>># Succeeded!

You have been warned! Use HTTPS URLs with httpimport!

Did I hear you say "Staging protocol for covertutils backdoors"?

Technique documentation on using httpimport to stage covertutils backdoor code, making EXE packed and unreadable code load non-included module dependencies.

About

Module for remote in-memory Python package/module loading through HTTP/S

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

httpimport

Python's missing feature!

Remote, in-memory Python package/moduleimporting through HTTP/S

PyPI version

A feature that Python2/3misses and has become popular in other languages is the remote loading of packages/modules.

httpimport lets a Python2/3 packages/modules to be imported directly in Python interpreter's process memory, through remote URIs, and more...

Example - In a Nutshell

>>>importhttpimport>>>httpimport.__all__
['HttpImporter', 'add_remote_repo', 'remove_remote_repo', 'remote_repo', 'github_repo', 'bitbucket_repo']
>>>withhttpimport.remote_repo(['package1','package2','package3'], 'http://my-codes.example.com/python_packages'):
... importpackage1
...
>>>withhttpimport.github_repo('operatorequals', 'covertutils', branch=master):
... importcovertutils
... # Also works with 'bitbucket_repo'
>>># A depends to B and B depends to C (A, B, C : Python modules/packages in different domains):>>># A exists in "repo_a.my-codes.example.com"	|>>># B exists in "repo_b.my-codes.example.com" | <-- Different domains>>># C exists in "repo_c.my-codes.example.com" |>>>withhttpimport.remote_repo(['C'], 'http://repo_c.my-codes.example.com/python_packages'):
... withhttpimport.remote_repo(['B'], 'http://repo_b.my-codes.example.com/python_packages'):
... withhttpimport.remote_repo(['A'], 'http://repo_a.my-codes.example.com/python_packages'):
... importA
... # Asks for A, Searches for B, Asks for B, Searches for C, Asks for C --> Resolves --> Imports A>>>
>>>module_object=httpimport.load('package1', 'http://my-codes.example.com/python_packages')
>>>module_object<module'package1'from'http://my-codes.example.com/python_packages/package1/__init__.py'>

Example - The Whole Picture

Using the SimpleHTTPServer, a whole directory can be served through HTTP as follows:

user@hostname:/tmp/test_directory$ ls -R
.:
test_package
./test_package:
__init__.py __init__.pyc module1.py module2.py
user@hostname:/tmp/test_directory$
user@hostname:/tmp/test_directory$ python -m SimpleHTTPServer &
[1] 9565
Serving HTTP on 0.0.0.0 port 8000 ...
user@hostname:/tmp/test_directory$
user@hostname:/tmp/test_directory$
user@hostname:/tmp/test_directory$ curl http://localhost:8000/test_package/module1.py
127.0.0.1 - - [22/Aug/2017 17:42:49] "GET /test_package/module1.py HTTP/1.1" 200 -
def dummy_func() :return'Function Loaded'
class dummy_class :
def dummy_method(self) :return'Class and method loaded'
dummy_str = 'Constant Loaded'
user@hostname:/tmp/test_directory$
user@hostname:/tmp/test_directory$ curl http://localhost:8000/test_package/__init__.py
127.0.0.1 - - [22/Aug/2017 17:45:20] "GET /test_package/__init__.py HTTP/1.1" 200 -
__all__ = ["module1", "module2"]

Using this simple built-in feature of Py2/3, a custom importer can been created, that given a base URL and a list of package names, it fetches and automatically loads all modules and packages to the local namespace.

Usage

Making the HTTP repo

user@hostname:/tmp/test_directory$ ls -R
.:
test_package
./test_package:
__init__.py __init__.pyc module1.py module2.py
user@hostname:/tmp/test_directory$
user@hostname:/tmp/test_directory$ python -m SimpleHTTPServer
Serving HTTP on 0.0.0.0 port 8000 ...

Importing Remotely

add_remote_repo() and remove_remote_repo()

These 2 functions will add and remove to the default sys.meta_path custom HttpImporter objects, given the URL they will look for packages/modules and a list of packages/modules its one can serve.

>>>importtest_packageTraceback (mostrecentcalllast):
File"<stdin>", line1, in<module>ImportError: Nomodulenamedtest_package>>>>>>fromhttpimportimportadd_remote_repo, remove_remote_repo>>># In the given URL the 'test_package/' is available>>>add_remote_repo(['test_package'], 'http://localhost:8000/') # >>>importtest_package>>>>>>remove_remote_repo('http://localhost:8000/')
>>>importtest_package.module1Traceback (mostrecentcalllast):
File"<stdin>", line1, in<module>ImportError: Nomodulenamedmodule1

The remote_repo() context

Adding and removing Remote Repos can be a pain, specially if there are packages that are available in more than one repos. So the with keyword does the trick again:

>>>fromhttpimportimportremote_repo>>>>>>>>>withremote_repo(['test_package'], 'http://localhost:8000/') :
... fromtest_packageimportmodule1
...
>>>>>>fromtest_packageimportmodule2Traceback (mostrecentcalllast):
File"<stdin>", line1, in<module>ImportError: cannotimportnamemodule2>>>module1.dummy_str'Constant Loaded'>>>module1.dummy_func<functiondummy_funcat0x7f7a8a170410>

Reload module (setting httpimport.RELOAD flag):

importimportlibimporthttpimporthttpimport.INSECURE=Truewithhttpimport.remote_repo(['mod'], 'http://localhost:8000/a'):
importmodprint(mod.module_name())
withhttpimport.remote_repo(['mod'], 'http://localhost:8000/b'):
importlib.reload(mod)
importmodprint(mod.module_name())
httpimport.RELOAD=True# Allow reload modulewithhttpimport.remote_repo(['mod'], 'http://localhost:8000/b'):
importlib.reload(mod)
importmodprint(mod.module_name())

The Tiny Test for your amusement

The test.py file contains a minimal test. Try changing working directories and package names and see what happens...

$ python test.py
serving at port 8000
127.0.0.1 - - [22/Aug/2017 17:36:44] code 404, message File not found
127.0.0.1 - - [22/Aug/2017 17:36:44] "GET /test_package/module1/__init__.py HTTP/1.1" 404 -
127.0.0.1 - - [22/Aug/2017 17:36:44] "GET /test_package/module1.py HTTP/1.1" 200 -
Constant Loaded
Function Loaded
Class and method loaded

The Github Use Case!

Such HTTP Servers (serving Python packages in a directory structured way) can be found in the wild, not only created with SimpleHTTPServer. Github repos can serve as Python HTTPS Repos as well!!!

Here is an example with my beloved covertutils project:

>>>>>>importcovertutilsTraceback (mostrecentcalllast):
File"<stdin>", line1, in<module>ImportError: Nomodulenamedcovertutils>>># covertutils is not available through normal import!>>>>>>covertutils_url='https://raw.githubusercontent.com/operatorequals/covertutils/master/'>>>>>>fromhttpimportimportremote_repo>>>>>>withremote_repo(['covertutils'], covertutils_url) :
... importcovertutils
...
>>>printcovertutils.__author__JohnTorakis-operatorequals

The dedicatedgithub_repo() context:

>>>fromhttpimportimportgithub_repo>>>withgithub_repo( 'operatorequals', 'covertutils', ) :
... importcovertutils
...
>>>covertutils.__author__'John Torakis - operatorequals'>>>

What about branches?

>>>fromhttpimportimportgithub_repo>>>withgithub_repo( 'operatorequals', 'covertutils', branch='py3_compatibility' ) :
... importcovertutils
...
>>>covertutils.__author__'John Torakis - operatorequals'>>>

And ad-hoc commits too?

What if you need to stick to a fixed -known to work- commit?

>>>fromhttpimportimportgithub_repo>>>withgithub_repo( 'operatorequals', 'covertutils', commit='cf3f78c77c437edf2c291bd5b4ed27e0a93e6a77' ) :
... importcovertutils
...
>>>covertutils.__author__'John Torakis - operatorequals'>>>

The newer sibling bitbucket_repo() (as of 0.5.9)

>>>withbitbucket_repo('atlassian', 'python-bitbucket', module='pybitbucket'):
... importpybitbucket
...
>>>

Recursive Dependencies

If package A requires module B and A exists in http://example.com/a_repo/, while B exists in http://example.com/b_repo/, then A can be imported using the following technique:

>>>fromhttpimportimportremote_repo>>>withremote_repo(['B'],"http://example.com/b_repo/") :
... withremote_repo(['A'],"http://example.com/a_repo/") :
... importA
... [!] 'B'notfoundinHTTPrepository. MovingtonextFinder.
>>>>>>A<module'A'from'http://example.com/a_repo/A/__init__.py'>>>>B<module'B'from'http://example.com/a_repo/B.py'>>>>

Any combination of packages and modules can be imported this way!

The [!] Warning was emitted by the HttpImporter object created for A, as it couldn't locate B, and passed control to the next Finder object, that happened to be the HttpImporter object created for B!

The load() function (as of 0.5.10)

The load() function was added to make module loading possible without Namespace pollution.

>>>importhttpimport>>>pack1=httpimport.load('random-package','http://localhost:8000/')
>>>pack1<module'random-package'from'http://localhost:8000//random-package/__init__.py'>>>>>>># Trying to load 'os' module from the URL will fail, as it won't delegate to to other Finders/Loaders.>>>httpimport.load('os','http://localhost:8000/')
[!] 'non-existent-package'notfoundinHTTPrepository. MovingtonextFinder.
Traceback (mostrecentcalllast):
File"<stdin>", line1, in<module>File"httpimport.py", line287, inloadraiseImportError("Module '%s' cannot be imported from '%s'"% (module_name, url) )
ImportError: Module'os'cannotbeimportedfrom'http://localhost:8000/'

And no data touches the disk, nor any virtual environment. The import happens just to the running Python process!

Life suddenly got simpler for Python module testing!!!

Imagine the breeze of testing Pull Requests and packages that you aren't sure they will work for you!

Debugging...

>>>fromhttpimportimport*>>>>>>importlogging>>>logging.getLogger('httpimport').setLevel(logging.DEBUG)
>>>>>>withgithub_repo('operatorequals','covertutils') :
... importcovertutils
...
FINDER=================
[!] Searchingcovertutils
[!] PathisNone
[@] CheckingifconnectionisHTTPSsecure>
[@] Checkingifindeclaredremotemodulenames>
[@] Checkingifbuilt-in>
[@] Checkingifitisnamerepetition>
[*]Module/Package'covertutils'canbeloaded!
LOADER=================
[+] Loadingcovertutils
[+] Tryingtoimportaspackagefrom: 'https://raw.githubusercontent.com/operatorequals/covertutils/master//covertutils/__init__.py'
[+] Importing'covertutils'
[+] Readytoexecute'covertutils'code
[+] 'covertutils'importedsuccesfully!
>>>

Beware: Huge Security Implications!

Using the httpimport with HTTP URLs is highly discouraged outside the localhost interface!

As HTTP traffic isn't encrypted and/or integrity checked (unlike HTTPS), it is trivial for a remote attacker to intercept the HTTP responses (via an ARP MiTM probably), and add arbitrary Python code to the downloaded packages/modules. This will directly result in Remote Code Execution to your current user's context! In other words, you get totally F*ed...

Preventing the disaster (setting httpimport.INSECURE flag):

>>>importhttpimport>>>>>># Importing from plain HTTP ...>>>httpimport.load('test_module', 'http://localhost:8000//')
[!] UsingnonHTTPSURLs ('http://localhost:8000//') canbeasecurityhazard!
[-] 'httpimport.INSECUREisnotset! Aborting...
Traceback (mostrecentcalllast):
File"<stdin>", line1, in<module>File"httpimport.py", line302, inloadraiseImportError("Module '%s' cannot be imported from URL: '%s'"% (module_name, url) )
ImportError: Module'test_module'cannotbeimportedfromURL: 'http://localhost:8000/'>>># ... Throws Error!>>>>>># Importing from plain HTTP has to be DELIBERATELY enabled!>>>httpimport.INSECURE=True>>>httpimport.load('test_module', 'http://localhost:8000//')
[!] UsingnonHTTPSURLs ('http://localhost:8000//') canbeasecurityhazard!
<module'test_module'from'http://localhost:8000//test_module.py'>>>># Succeeded!

You have been warned! Use HTTPS URLs with httpimport!

Did I hear you say "Staging protocol for covertutils backdoors"?

Technique documentation on using httpimport to stage covertutils backdoor code, making EXE packed and unreadable code load non-included module dependencies.

About

Module for remote in-memory Python package/module loading through HTTP/S

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

httpimport

Python's missing feature!

Remote, in-memory Python package/moduleimporting through HTTP/S

PyPI version

A feature that Python2/3misses and has become popular in other languages is the remote loading of packages/modules.

httpimport lets a Python2/3 packages/modules to be imported directly in Python interpreter's process memory, through remote URIs, and more...

Example - In a Nutshell

>>>importhttpimport>>>httpimport.__all__
['HttpImporter', 'add_remote_repo', 'remove_remote_repo', 'remote_repo', 'github_repo', 'bitbucket_repo']
>>>withhttpimport.remote_repo(['package1','package2','package3'], 'http://my-codes.example.com/python_packages'):
... importpackage1
...
>>>withhttpimport.github_repo('operatorequals', 'covertutils', branch=master):
... importcovertutils
... # Also works with 'bitbucket_repo'
>>># A depends to B and B depends to C (A, B, C : Python modules/packages in different domains):>>># A exists in "repo_a.my-codes.example.com"	|>>># B exists in "repo_b.my-codes.example.com" | <-- Different domains>>># C exists in "repo_c.my-codes.example.com" |>>>withhttpimport.remote_repo(['C'], 'http://repo_c.my-codes.example.com/python_packages'):
... withhttpimport.remote_repo(['B'], 'http://repo_b.my-codes.example.com/python_packages'):
... withhttpimport.remote_repo(['A'], 'http://repo_a.my-codes.example.com/python_packages'):
... importA
... # Asks for A, Searches for B, Asks for B, Searches for C, Asks for C --> Resolves --> Imports A>>>
>>>module_object=httpimport.load('package1', 'http://my-codes.example.com/python_packages')
>>>module_object<module'package1'from'http://my-codes.example.com/python_packages/package1/__init__.py'>

Example - The Whole Picture

Using the SimpleHTTPServer, a whole directory can be served through HTTP as follows:

user@hostname:/tmp/test_directory$ ls -R
.:
test_package
./test_package:
__init__.py __init__.pyc module1.py module2.py
user@hostname:/tmp/test_directory$
user@hostname:/tmp/test_directory$ python -m SimpleHTTPServer &
[1] 9565
Serving HTTP on 0.0.0.0 port 8000 ...
user@hostname:/tmp/test_directory$
user@hostname:/tmp/test_directory$
user@hostname:/tmp/test_directory$ curl http://localhost:8000/test_package/module1.py
127.0.0.1 - - [22/Aug/2017 17:42:49] "GET /test_package/module1.py HTTP/1.1" 200 -
def dummy_func() :return'Function Loaded'
class dummy_class :
def dummy_method(self) :return'Class and method loaded'
dummy_str = 'Constant Loaded'
user@hostname:/tmp/test_directory$
user@hostname:/tmp/test_directory$ curl http://localhost:8000/test_package/__init__.py
127.0.0.1 - - [22/Aug/2017 17:45:20] "GET /test_package/__init__.py HTTP/1.1" 200 -
__all__ = ["module1", "module2"]

Using this simple built-in feature of Py2/3, a custom importer can been created, that given a base URL and a list of package names, it fetches and automatically loads all modules and packages to the local namespace.

Usage

Making the HTTP repo

user@hostname:/tmp/test_directory$ ls -R
.:
test_package
./test_package:
__init__.py __init__.pyc module1.py module2.py
user@hostname:/tmp/test_directory$
user@hostname:/tmp/test_directory$ python -m SimpleHTTPServer
Serving HTTP on 0.0.0.0 port 8000 ...

Importing Remotely

add_remote_repo() and remove_remote_repo()

These 2 functions will add and remove to the default sys.meta_path custom HttpImporter objects, given the URL they will look for packages/modules and a list of packages/modules its one can serve.

>>>importtest_packageTraceback (mostrecentcalllast):
File"<stdin>", line1, in<module>ImportError: Nomodulenamedtest_package>>>>>>fromhttpimportimportadd_remote_repo, remove_remote_repo>>># In the given URL the 'test_package/' is available>>>add_remote_repo(['test_package'], 'http://localhost:8000/') # >>>importtest_package>>>>>>remove_remote_repo('http://localhost:8000/')
>>>importtest_package.module1Traceback (mostrecentcalllast):
File"<stdin>", line1, in<module>ImportError: Nomodulenamedmodule1

The remote_repo() context

Adding and removing Remote Repos can be a pain, specially if there are packages that are available in more than one repos. So the with keyword does the trick again:

>>>fromhttpimportimportremote_repo>>>>>>>>>withremote_repo(['test_package'], 'http://localhost:8000/') :
... fromtest_packageimportmodule1
...
>>>>>>fromtest_packageimportmodule2Traceback (mostrecentcalllast):
File"<stdin>", line1, in<module>ImportError: cannotimportnamemodule2>>>module1.dummy_str'Constant Loaded'>>>module1.dummy_func<functiondummy_funcat0x7f7a8a170410>

Reload module (setting httpimport.RELOAD flag):

importimportlibimporthttpimporthttpimport.INSECURE=Truewithhttpimport.remote_repo(['mod'], 'http://localhost:8000/a'):
importmodprint(mod.module_name())
withhttpimport.remote_repo(['mod'], 'http://localhost:8000/b'):
importlib.reload(mod)
importmodprint(mod.module_name())
httpimport.RELOAD=True# Allow reload modulewithhttpimport.remote_repo(['mod'], 'http://localhost:8000/b'):
importlib.reload(mod)
importmodprint(mod.module_name())

The Tiny Test for your amusement

The test.py file contains a minimal test. Try changing working directories and package names and see what happens...

$ python test.py
serving at port 8000
127.0.0.1 - - [22/Aug/2017 17:36:44] code 404, message File not found
127.0.0.1 - - [22/Aug/2017 17:36:44] "GET /test_package/module1/__init__.py HTTP/1.1" 404 -
127.0.0.1 - - [22/Aug/2017 17:36:44] "GET /test_package/module1.py HTTP/1.1" 200 -
Constant Loaded
Function Loaded
Class and method loaded

The Github Use Case!

Such HTTP Servers (serving Python packages in a directory structured way) can be found in the wild, not only created with SimpleHTTPServer. Github repos can serve as Python HTTPS Repos as well!!!

Here is an example with my beloved covertutils project:

>>>>>>importcovertutilsTraceback (mostrecentcalllast):
File"<stdin>", line1, in<module>ImportError: Nomodulenamedcovertutils>>># covertutils is not available through normal import!>>>>>>covertutils_url='https://raw.githubusercontent.com/operatorequals/covertutils/master/'>>>>>>fromhttpimportimportremote_repo>>>>>>withremote_repo(['covertutils'], covertutils_url) :
... importcovertutils
...
>>>printcovertutils.__author__JohnTorakis-operatorequals

The dedicatedgithub_repo() context:

>>>fromhttpimportimportgithub_repo>>>withgithub_repo( 'operatorequals', 'covertutils', ) :
... importcovertutils
...
>>>covertutils.__author__'John Torakis - operatorequals'>>>

What about branches?

>>>fromhttpimportimportgithub_repo>>>withgithub_repo( 'operatorequals', 'covertutils', branch='py3_compatibility' ) :
... importcovertutils
...
>>>covertutils.__author__'John Torakis - operatorequals'>>>

And ad-hoc commits too?

What if you need to stick to a fixed -known to work- commit?

>>>fromhttpimportimportgithub_repo>>>withgithub_repo( 'operatorequals', 'covertutils', commit='cf3f78c77c437edf2c291bd5b4ed27e0a93e6a77' ) :
... importcovertutils
...
>>>covertutils.__author__'John Torakis - operatorequals'>>>

The newer sibling bitbucket_repo() (as of 0.5.9)

>>>withbitbucket_repo('atlassian', 'python-bitbucket', module='pybitbucket'):
... importpybitbucket
...
>>>

Recursive Dependencies

If package A requires module B and A exists in http://example.com/a_repo/, while B exists in http://example.com/b_repo/, then A can be imported using the following technique:

>>>fromhttpimportimportremote_repo>>>withremote_repo(['B'],"http://example.com/b_repo/") :
... withremote_repo(['A'],"http://example.com/a_repo/") :
... importA
... [!] 'B'notfoundinHTTPrepository. MovingtonextFinder.
>>>>>>A<module'A'from'http://example.com/a_repo/A/__init__.py'>>>>B<module'B'from'http://example.com/a_repo/B.py'>>>>

Any combination of packages and modules can be imported this way!

The [!] Warning was emitted by the HttpImporter object created for A, as it couldn't locate B, and passed control to the next Finder object, that happened to be the HttpImporter object created for B!

The load() function (as of 0.5.10)

The load() function was added to make module loading possible without Namespace pollution.

>>>importhttpimport>>>pack1=httpimport.load('random-package','http://localhost:8000/')
>>>pack1<module'random-package'from'http://localhost:8000//random-package/__init__.py'>>>>>>># Trying to load 'os' module from the URL will fail, as it won't delegate to to other Finders/Loaders.>>>httpimport.load('os','http://localhost:8000/')
[!] 'non-existent-package'notfoundinHTTPrepository. MovingtonextFinder.
Traceback (mostrecentcalllast):
File"<stdin>", line1, in<module>File"httpimport.py", line287, inloadraiseImportError("Module '%s' cannot be imported from '%s'"% (module_name, url) )
ImportError: Module'os'cannotbeimportedfrom'http://localhost:8000/'

And no data touches the disk, nor any virtual environment. The import happens just to the running Python process!

Life suddenly got simpler for Python module testing!!!

Imagine the breeze of testing Pull Requests and packages that you aren't sure they will work for you!

Debugging...

>>>fromhttpimportimport*>>>>>>importlogging>>>logging.getLogger('httpimport').setLevel(logging.DEBUG)
>>>>>>withgithub_repo('operatorequals','covertutils') :
... importcovertutils
...
FINDER=================
[!] Searchingcovertutils
[!] PathisNone
[@] CheckingifconnectionisHTTPSsecure>
[@] Checkingifindeclaredremotemodulenames>
[@] Checkingifbuilt-in>
[@] Checkingifitisnamerepetition>
[*]Module/Package'covertutils'canbeloaded!
LOADER=================
[+] Loadingcovertutils
[+] Tryingtoimportaspackagefrom: 'https://raw.githubusercontent.com/operatorequals/covertutils/master//covertutils/__init__.py'
[+] Importing'covertutils'
[+] Readytoexecute'covertutils'code
[+] 'covertutils'importedsuccesfully!
>>>

Beware: Huge Security Implications!

Using the httpimport with HTTP URLs is highly discouraged outside the localhost interface!

As HTTP traffic isn't encrypted and/or integrity checked (unlike HTTPS), it is trivial for a remote attacker to intercept the HTTP responses (via an ARP MiTM probably), and add arbitrary Python code to the downloaded packages/modules. This will directly result in Remote Code Execution to your current user's context! In other words, you get totally F*ed...

Preventing the disaster (setting httpimport.INSECURE flag):

>>>importhttpimport>>>>>># Importing from plain HTTP ...>>>httpimport.load('test_module', 'http://localhost:8000//')
[!] UsingnonHTTPSURLs ('http://localhost:8000//') canbeasecurityhazard!
[-] 'httpimport.INSECUREisnotset! Aborting...
Traceback (mostrecentcalllast):
File"<stdin>", line1, in<module>File"httpimport.py", line302, inloadraiseImportError("Module '%s' cannot be imported from URL: '%s'"% (module_name, url) )
ImportError: Module'test_module'cannotbeimportedfromURL: 'http://localhost:8000/'>>># ... Throws Error!>>>>>># Importing from plain HTTP has to be DELIBERATELY enabled!>>>httpimport.INSECURE=True>>>httpimport.load('test_module', 'http://localhost:8000//')
[!] UsingnonHTTPSURLs ('http://localhost:8000//') canbeasecurityhazard!
<module'test_module'from'http://localhost:8000//test_module.py'>>>># Succeeded!

You have been warned! Use HTTPS URLs with httpimport!

Did I hear you say "Staging protocol for covertutils backdoors"?

Technique documentation on using httpimport to stage covertutils backdoor code, making EXE packed and unreadable code load non-included module dependencies.

About

Module for remote in-memory Python package/module loading through HTTP/S

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

httpimport

Python's missing feature!

Remote, in-memory Python package/moduleimporting through HTTP/S

PyPI version

A feature that Python2/3misses and has become popular in other languages is the remote loading of packages/modules.

httpimport lets a Python2/3 packages/modules to be imported directly in Python interpreter's process memory, through remote URIs, and more...

Example - In a Nutshell

>>>importhttpimport>>>httpimport.__all__
['HttpImporter', 'add_remote_repo', 'remove_remote_repo', 'remote_repo', 'github_repo', 'bitbucket_repo']
>>>withhttpimport.remote_repo(['package1','package2','package3'], 'http://my-codes.example.com/python_packages'):
... importpackage1
...
>>>withhttpimport.github_repo('operatorequals', 'covertutils', branch=master):
... importcovertutils
... # Also works with 'bitbucket_repo'
>>># A depends to B and B depends to C (A, B, C : Python modules/packages in different domains):>>># A exists in "repo_a.my-codes.example.com"	|>>># B exists in "repo_b.my-codes.example.com" | <-- Different domains>>># C exists in "repo_c.my-codes.example.com" |>>>withhttpimport.remote_repo(['C'], 'http://repo_c.my-codes.example.com/python_packages'):
... withhttpimport.remote_repo(['B'], 'http://repo_b.my-codes.example.com/python_packages'):
... withhttpimport.remote_repo(['A'], 'http://repo_a.my-codes.example.com/python_packages'):
... importA
... # Asks for A, Searches for B, Asks for B, Searches for C, Asks for C --> Resolves --> Imports A>>>
>>>module_object=httpimport.load('package1', 'http://my-codes.example.com/python_packages')
>>>module_object<module'package1'from'http://my-codes.example.com/python_packages/package1/__init__.py'>

Example - The Whole Picture

Using the SimpleHTTPServer, a whole directory can be served through HTTP as follows:

user@hostname:/tmp/test_directory$ ls -R
.:
test_package
./test_package:
__init__.py __init__.pyc module1.py module2.py
user@hostname:/tmp/test_directory$
user@hostname:/tmp/test_directory$ python -m SimpleHTTPServer &
[1] 9565
Serving HTTP on 0.0.0.0 port 8000 ...
user@hostname:/tmp/test_directory$
user@hostname:/tmp/test_directory$
user@hostname:/tmp/test_directory$ curl http://localhost:8000/test_package/module1.py
127.0.0.1 - - [22/Aug/2017 17:42:49] "GET /test_package/module1.py HTTP/1.1" 200 -
def dummy_func() :return'Function Loaded'
class dummy_class :
def dummy_method(self) :return'Class and method loaded'
dummy_str = 'Constant Loaded'
user@hostname:/tmp/test_directory$
user@hostname:/tmp/test_directory$ curl http://localhost:8000/test_package/__init__.py
127.0.0.1 - - [22/Aug/2017 17:45:20] "GET /test_package/__init__.py HTTP/1.1" 200 -
__all__ = ["module1", "module2"]

Using this simple built-in feature of Py2/3, a custom importer can been created, that given a base URL and a list of package names, it fetches and automatically loads all modules and packages to the local namespace.

Usage

Making the HTTP repo

user@hostname:/tmp/test_directory$ ls -R
.:
test_package
./test_package:
__init__.py __init__.pyc module1.py module2.py
user@hostname:/tmp/test_directory$
user@hostname:/tmp/test_directory$ python -m SimpleHTTPServer
Serving HTTP on 0.0.0.0 port 8000 ...

Importing Remotely

add_remote_repo() and remove_remote_repo()

These 2 functions will add and remove to the default sys.meta_path custom HttpImporter objects, given the URL they will look for packages/modules and a list of packages/modules its one can serve.

>>>importtest_packageTraceback (mostrecentcalllast):
File"<stdin>", line1, in<module>ImportError: Nomodulenamedtest_package>>>>>>fromhttpimportimportadd_remote_repo, remove_remote_repo>>># In the given URL the 'test_package/' is available>>>add_remote_repo(['test_package'], 'http://localhost:8000/') # >>>importtest_package>>>>>>remove_remote_repo('http://localhost:8000/')
>>>importtest_package.module1Traceback (mostrecentcalllast):
File"<stdin>", line1, in<module>ImportError: Nomodulenamedmodule1

The remote_repo() context

Adding and removing Remote Repos can be a pain, specially if there are packages that are available in more than one repos. So the with keyword does the trick again:

>>>fromhttpimportimportremote_repo>>>>>>>>>withremote_repo(['test_package'], 'http://localhost:8000/') :
... fromtest_packageimportmodule1
...
>>>>>>fromtest_packageimportmodule2Traceback (mostrecentcalllast):
File"<stdin>", line1, in<module>ImportError: cannotimportnamemodule2>>>module1.dummy_str'Constant Loaded'>>>module1.dummy_func<functiondummy_funcat0x7f7a8a170410>

Reload module (setting httpimport.RELOAD flag):

importimportlibimporthttpimporthttpimport.INSECURE=Truewithhttpimport.remote_repo(['mod'], 'http://localhost:8000/a'):
importmodprint(mod.module_name())
withhttpimport.remote_repo(['mod'], 'http://localhost:8000/b'):
importlib.reload(mod)
importmodprint(mod.module_name())
httpimport.RELOAD=True# Allow reload modulewithhttpimport.remote_repo(['mod'], 'http://localhost:8000/b'):
importlib.reload(mod)
importmodprint(mod.module_name())

The Tiny Test for your amusement

The test.py file contains a minimal test. Try changing working directories and package names and see what happens...

$ python test.py
serving at port 8000
127.0.0.1 - - [22/Aug/2017 17:36:44] code 404, message File not found
127.0.0.1 - - [22/Aug/2017 17:36:44] "GET /test_package/module1/__init__.py HTTP/1.1" 404 -
127.0.0.1 - - [22/Aug/2017 17:36:44] "GET /test_package/module1.py HTTP/1.1" 200 -
Constant Loaded
Function Loaded
Class and method loaded

The Github Use Case!

Such HTTP Servers (serving Python packages in a directory structured way) can be found in the wild, not only created with SimpleHTTPServer. Github repos can serve as Python HTTPS Repos as well!!!

Here is an example with my beloved covertutils project:

>>>>>>importcovertutilsTraceback (mostrecentcalllast):
File"<stdin>", line1, in<module>ImportError: Nomodulenamedcovertutils>>># covertutils is not available through normal import!>>>>>>covertutils_url='https://raw.githubusercontent.com/operatorequals/covertutils/master/'>>>>>>fromhttpimportimportremote_repo>>>>>>withremote_repo(['covertutils'], covertutils_url) :
... importcovertutils
...
>>>printcovertutils.__author__JohnTorakis-operatorequals

The dedicatedgithub_repo() context:

>>>fromhttpimportimportgithub_repo>>>withgithub_repo( 'operatorequals', 'covertutils', ) :
... importcovertutils
...
>>>covertutils.__author__'John Torakis - operatorequals'>>>

What about branches?

>>>fromhttpimportimportgithub_repo>>>withgithub_repo( 'operatorequals', 'covertutils', branch='py3_compatibility' ) :
... importcovertutils
...
>>>covertutils.__author__'John Torakis - operatorequals'>>>

And ad-hoc commits too?

What if you need to stick to a fixed -known to work- commit?

>>>fromhttpimportimportgithub_repo>>>withgithub_repo( 'operatorequals', 'covertutils', commit='cf3f78c77c437edf2c291bd5b4ed27e0a93e6a77' ) :
... importcovertutils
...
>>>covertutils.__author__'John Torakis - operatorequals'>>>

The newer sibling bitbucket_repo() (as of 0.5.9)

>>>withbitbucket_repo('atlassian', 'python-bitbucket', module='pybitbucket'):
... importpybitbucket
...
>>>

Recursive Dependencies

If package A requires module B and A exists in http://example.com/a_repo/, while B exists in http://example.com/b_repo/, then A can be imported using the following technique:

>>>fromhttpimportimportremote_repo>>>withremote_repo(['B'],"http://example.com/b_repo/") :
... withremote_repo(['A'],"http://example.com/a_repo/") :
... importA
... [!] 'B'notfoundinHTTPrepository. MovingtonextFinder.
>>>>>>A<module'A'from'http://example.com/a_repo/A/__init__.py'>>>>B<module'B'from'http://example.com/a_repo/B.py'>>>>

Any combination of packages and modules can be imported this way!

The [!] Warning was emitted by the HttpImporter object created for A, as it couldn't locate B, and passed control to the next Finder object, that happened to be the HttpImporter object created for B!

The load() function (as of 0.5.10)

The load() function was added to make module loading possible without Namespace pollution.

>>>importhttpimport>>>pack1=httpimport.load('random-package','http://localhost:8000/')
>>>pack1<module'random-package'from'http://localhost:8000//random-package/__init__.py'>>>>>>># Trying to load 'os' module from the URL will fail, as it won't delegate to to other Finders/Loaders.>>>httpimport.load('os','http://localhost:8000/')
[!] 'non-existent-package'notfoundinHTTPrepository. MovingtonextFinder.
Traceback (mostrecentcalllast):
File"<stdin>", line1, in<module>File"httpimport.py", line287, inloadraiseImportError("Module '%s' cannot be imported from '%s'"% (module_name, url) )
ImportError: Module'os'cannotbeimportedfrom'http://localhost:8000/'

And no data touches the disk, nor any virtual environment. The import happens just to the running Python process!

Life suddenly got simpler for Python module testing!!!

Imagine the breeze of testing Pull Requests and packages that you aren't sure they will work for you!

Debugging...

>>>fromhttpimportimport*>>>>>>importlogging>>>logging.getLogger('httpimport').setLevel(logging.DEBUG)
>>>>>>withgithub_repo('operatorequals','covertutils') :
... importcovertutils
...
FINDER=================
[!] Searchingcovertutils
[!] PathisNone
[@] CheckingifconnectionisHTTPSsecure>
[@] Checkingifindeclaredremotemodulenames>
[@] Checkingifbuilt-in>
[@] Checkingifitisnamerepetition>
[*]Module/Package'covertutils'canbeloaded!
LOADER=================
[+] Loadingcovertutils
[+] Tryingtoimportaspackagefrom: 'https://raw.githubusercontent.com/operatorequals/covertutils/master//covertutils/__init__.py'
[+] Importing'covertutils'
[+] Readytoexecute'covertutils'code
[+] 'covertutils'importedsuccesfully!
>>>

Beware: Huge Security Implications!

Using the httpimport with HTTP URLs is highly discouraged outside the localhost interface!

As HTTP traffic isn't encrypted and/or integrity checked (unlike HTTPS), it is trivial for a remote attacker to intercept the HTTP responses (via an ARP MiTM probably), and add arbitrary Python code to the downloaded packages/modules. This will directly result in Remote Code Execution to your current user's context! In other words, you get totally F*ed...

Preventing the disaster (setting httpimport.INSECURE flag):

>>>importhttpimport>>>>>># Importing from plain HTTP ...>>>httpimport.load('test_module', 'http://localhost:8000//')
[!] UsingnonHTTPSURLs ('http://localhost:8000//') canbeasecurityhazard!
[-] 'httpimport.INSECUREisnotset! Aborting...
Traceback (mostrecentcalllast):
File"<stdin>", line1, in<module>File"httpimport.py", line302, inloadraiseImportError("Module '%s' cannot be imported from URL: '%s'"% (module_name, url) )
ImportError: Module'test_module'cannotbeimportedfromURL: 'http://localhost:8000/'>>># ... Throws Error!>>>>>># Importing from plain HTTP has to be DELIBERATELY enabled!>>>httpimport.INSECURE=True>>>httpimport.load('test_module', 'http://localhost:8000//')
[!] UsingnonHTTPSURLs ('http://localhost:8000//') canbeasecurityhazard!
<module'test_module'from'http://localhost:8000//test_module.py'>>>># Succeeded!

You have been warned! Use HTTPS URLs with httpimport!

Did I hear you say "Staging protocol for covertutils backdoors"?

Technique documentation on using httpimport to stage covertutils backdoor code, making EXE packed and unreadable code load non-included module dependencies.

About

Module for remote in-memory Python package/module loading through HTTP/S

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

httpimport

Python's missing feature!

Remote, in-memory Python package/moduleimporting through HTTP/S

PyPI version

A feature that Python2/3misses and has become popular in other languages is the remote loading of packages/modules.

httpimport lets a Python2/3 packages/modules to be imported directly in Python interpreter's process memory, through remote URIs, and more...

Example - In a Nutshell

>>>importhttpimport>>>httpimport.__all__
['HttpImporter', 'add_remote_repo', 'remove_remote_repo', 'remote_repo', 'github_repo', 'bitbucket_repo']
>>>withhttpimport.remote_repo(['package1','package2','package3'], 'http://my-codes.example.com/python_packages'):
... importpackage1
...
>>>withhttpimport.github_repo('operatorequals', 'covertutils', branch=master):
... importcovertutils
... # Also works with 'bitbucket_repo'
>>># A depends to B and B depends to C (A, B, C : Python modules/packages in different domains):>>># A exists in "repo_a.my-codes.example.com"	|>>># B exists in "repo_b.my-codes.example.com" | <-- Different domains>>># C exists in "repo_c.my-codes.example.com" |>>>withhttpimport.remote_repo(['C'], 'http://repo_c.my-codes.example.com/python_packages'):
... withhttpimport.remote_repo(['B'], 'http://repo_b.my-codes.example.com/python_packages'):
... withhttpimport.remote_repo(['A'], 'http://repo_a.my-codes.example.com/python_packages'):
... importA
... # Asks for A, Searches for B, Asks for B, Searches for C, Asks for C --> Resolves --> Imports A>>>
>>>module_object=httpimport.load('package1', 'http://my-codes.example.com/python_packages')
>>>module_object<module'package1'from'http://my-codes.example.com/python_packages/package1/__init__.py'>

Example - The Whole Picture

Using the SimpleHTTPServer, a whole directory can be served through HTTP as follows:

user@hostname:/tmp/test_directory$ ls -R
.:
test_package
./test_package:
__init__.py __init__.pyc module1.py module2.py
user@hostname:/tmp/test_directory$
user@hostname:/tmp/test_directory$ python -m SimpleHTTPServer &
[1] 9565
Serving HTTP on 0.0.0.0 port 8000 ...
user@hostname:/tmp/test_directory$
user@hostname:/tmp/test_directory$
user@hostname:/tmp/test_directory$ curl http://localhost:8000/test_package/module1.py
127.0.0.1 - - [22/Aug/2017 17:42:49] "GET /test_package/module1.py HTTP/1.1" 200 -
def dummy_func() :return'Function Loaded'
class dummy_class :
def dummy_method(self) :return'Class and method loaded'
dummy_str = 'Constant Loaded'
user@hostname:/tmp/test_directory$
user@hostname:/tmp/test_directory$ curl http://localhost:8000/test_package/__init__.py
127.0.0.1 - - [22/Aug/2017 17:45:20] "GET /test_package/__init__.py HTTP/1.1" 200 -
__all__ = ["module1", "module2"]

Using this simple built-in feature of Py2/3, a custom importer can been created, that given a base URL and a list of package names, it fetches and automatically loads all modules and packages to the local namespace.

Usage

Making the HTTP repo

user@hostname:/tmp/test_directory$ ls -R
.:
test_package
./test_package:
__init__.py __init__.pyc module1.py module2.py
user@hostname:/tmp/test_directory$
user@hostname:/tmp/test_directory$ python -m SimpleHTTPServer
Serving HTTP on 0.0.0.0 port 8000 ...

Importing Remotely

add_remote_repo() and remove_remote_repo()

These 2 functions will add and remove to the default sys.meta_path custom HttpImporter objects, given the URL they will look for packages/modules and a list of packages/modules its one can serve.

>>>importtest_packageTraceback (mostrecentcalllast):
File"<stdin>", line1, in<module>ImportError: Nomodulenamedtest_package>>>>>>fromhttpimportimportadd_remote_repo, remove_remote_repo>>># In the given URL the 'test_package/' is available>>>add_remote_repo(['test_package'], 'http://localhost:8000/') # >>>importtest_package>>>>>>remove_remote_repo('http://localhost:8000/')
>>>importtest_package.module1Traceback (mostrecentcalllast):
File"<stdin>", line1, in<module>ImportError: Nomodulenamedmodule1

The remote_repo() context

Adding and removing Remote Repos can be a pain, specially if there are packages that are available in more than one repos. So the with keyword does the trick again:

>>>fromhttpimportimportremote_repo>>>>>>>>>withremote_repo(['test_package'], 'http://localhost:8000/') :
... fromtest_packageimportmodule1
...
>>>>>>fromtest_packageimportmodule2Traceback (mostrecentcalllast):
File"<stdin>", line1, in<module>ImportError: cannotimportnamemodule2>>>module1.dummy_str'Constant Loaded'>>>module1.dummy_func<functiondummy_funcat0x7f7a8a170410>

Reload module (setting httpimport.RELOAD flag):

importimportlibimporthttpimporthttpimport.INSECURE=Truewithhttpimport.remote_repo(['mod'], 'http://localhost:8000/a'):
importmodprint(mod.module_name())
withhttpimport.remote_repo(['mod'], 'http://localhost:8000/b'):
importlib.reload(mod)
importmodprint(mod.module_name())
httpimport.RELOAD=True# Allow reload modulewithhttpimport.remote_repo(['mod'], 'http://localhost:8000/b'):
importlib.reload(mod)
importmodprint(mod.module_name())

The Tiny Test for your amusement

The test.py file contains a minimal test. Try changing working directories and package names and see what happens...

$ python test.py
serving at port 8000
127.0.0.1 - - [22/Aug/2017 17:36:44] code 404, message File not found
127.0.0.1 - - [22/Aug/2017 17:36:44] "GET /test_package/module1/__init__.py HTTP/1.1" 404 -
127.0.0.1 - - [22/Aug/2017 17:36:44] "GET /test_package/module1.py HTTP/1.1" 200 -
Constant Loaded
Function Loaded
Class and method loaded

The Github Use Case!

Such HTTP Servers (serving Python packages in a directory structured way) can be found in the wild, not only created with SimpleHTTPServer. Github repos can serve as Python HTTPS Repos as well!!!

Here is an example with my beloved covertutils project:

>>>>>>importcovertutilsTraceback (mostrecentcalllast):
File"<stdin>", line1, in<module>ImportError: Nomodulenamedcovertutils>>># covertutils is not available through normal import!>>>>>>covertutils_url='https://raw.githubusercontent.com/operatorequals/covertutils/master/'>>>>>>fromhttpimportimportremote_repo>>>>>>withremote_repo(['covertutils'], covertutils_url) :
... importcovertutils
...
>>>printcovertutils.__author__JohnTorakis-operatorequals

The dedicatedgithub_repo() context:

>>>fromhttpimportimportgithub_repo>>>withgithub_repo( 'operatorequals', 'covertutils', ) :
... importcovertutils
...
>>>covertutils.__author__'John Torakis - operatorequals'>>>

What about branches?

>>>fromhttpimportimportgithub_repo>>>withgithub_repo( 'operatorequals', 'covertutils', branch='py3_compatibility' ) :
... importcovertutils
...
>>>covertutils.__author__'John Torakis - operatorequals'>>>

And ad-hoc commits too?

What if you need to stick to a fixed -known to work- commit?

>>>fromhttpimportimportgithub_repo>>>withgithub_repo( 'operatorequals', 'covertutils', commit='cf3f78c77c437edf2c291bd5b4ed27e0a93e6a77' ) :
... importcovertutils
...
>>>covertutils.__author__'John Torakis - operatorequals'>>>

The newer sibling bitbucket_repo() (as of 0.5.9)

>>>withbitbucket_repo('atlassian', 'python-bitbucket', module='pybitbucket'):
... importpybitbucket
...
>>>

Recursive Dependencies

If package A requires module B and A exists in http://example.com/a_repo/, while B exists in http://example.com/b_repo/, then A can be imported using the following technique:

>>>fromhttpimportimportremote_repo>>>withremote_repo(['B'],"http://example.com/b_repo/") :
... withremote_repo(['A'],"http://example.com/a_repo/") :
... importA
... [!] 'B'notfoundinHTTPrepository. MovingtonextFinder.
>>>>>>A<module'A'from'http://example.com/a_repo/A/__init__.py'>>>>B<module'B'from'http://example.com/a_repo/B.py'>>>>

Any combination of packages and modules can be imported this way!

The [!] Warning was emitted by the HttpImporter object created for A, as it couldn't locate B, and passed control to the next Finder object, that happened to be the HttpImporter object created for B!

The load() function (as of 0.5.10)

The load() function was added to make module loading possible without Namespace pollution.

>>>importhttpimport>>>pack1=httpimport.load('random-package','http://localhost:8000/')
>>>pack1<module'random-package'from'http://localhost:8000//random-package/__init__.py'>>>>>>># Trying to load 'os' module from the URL will fail, as it won't delegate to to other Finders/Loaders.>>>httpimport.load('os','http://localhost:8000/')
[!] 'non-existent-package'notfoundinHTTPrepository. MovingtonextFinder.
Traceback (mostrecentcalllast):
File"<stdin>", line1, in<module>File"httpimport.py", line287, inloadraiseImportError("Module '%s' cannot be imported from '%s'"% (module_name, url) )
ImportError: Module'os'cannotbeimportedfrom'http://localhost:8000/'

And no data touches the disk, nor any virtual environment. The import happens just to the running Python process!

Life suddenly got simpler for Python module testing!!!

Imagine the breeze of testing Pull Requests and packages that you aren't sure they will work for you!

Debugging...

>>>fromhttpimportimport*>>>>>>importlogging>>>logging.getLogger('httpimport').setLevel(logging.DEBUG)
>>>>>>withgithub_repo('operatorequals','covertutils') :
... importcovertutils
...
FINDER=================
[!] Searchingcovertutils
[!] PathisNone
[@] CheckingifconnectionisHTTPSsecure>
[@] Checkingifindeclaredremotemodulenames>
[@] Checkingifbuilt-in>
[@] Checkingifitisnamerepetition>
[*]Module/Package'covertutils'canbeloaded!
LOADER=================
[+] Loadingcovertutils
[+] Tryingtoimportaspackagefrom: 'https://raw.githubusercontent.com/operatorequals/covertutils/master//covertutils/__init__.py'
[+] Importing'covertutils'
[+] Readytoexecute'covertutils'code
[+] 'covertutils'importedsuccesfully!
>>>

Beware: Huge Security Implications!

Using the httpimport with HTTP URLs is highly discouraged outside the localhost interface!

As HTTP traffic isn't encrypted and/or integrity checked (unlike HTTPS), it is trivial for a remote attacker to intercept the HTTP responses (via an ARP MiTM probably), and add arbitrary Python code to the downloaded packages/modules. This will directly result in Remote Code Execution to your current user's context! In other words, you get totally F*ed...

Preventing the disaster (setting httpimport.INSECURE flag):

>>>importhttpimport>>>>>># Importing from plain HTTP ...>>>httpimport.load('test_module', 'http://localhost:8000//')
[!] UsingnonHTTPSURLs ('http://localhost:8000//') canbeasecurityhazard!
[-] 'httpimport.INSECUREisnotset! Aborting...
Traceback (mostrecentcalllast):
File"<stdin>", line1, in<module>File"httpimport.py", line302, inloadraiseImportError("Module '%s' cannot be imported from URL: '%s'"% (module_name, url) )
ImportError: Module'test_module'cannotbeimportedfromURL: 'http://localhost:8000/'>>># ... Throws Error!>>>>>># Importing from plain HTTP has to be DELIBERATELY enabled!>>>httpimport.INSECURE=True>>>httpimport.load('test_module', 'http://localhost:8000//')
[!] UsingnonHTTPSURLs ('http://localhost:8000//') canbeasecurityhazard!
<module'test_module'from'http://localhost:8000//test_module.py'>>>># Succeeded!

You have been warned! Use HTTPS URLs with httpimport!

Did I hear you say "Staging protocol for covertutils backdoors"?

Technique documentation on using httpimport to stage covertutils backdoor code, making EXE packed and unreadable code load non-included module dependencies.

About

Module for remote in-memory Python package/module loading through HTTP/S

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages