473,322 Members | 1,307 Online
Bytes | Software Development & Data Engineering Community
Post Job

Home Posts Topics Members FAQ

Join Bytes to post your question to a community of 473,322 software developers and data experts.

Taint (like in Perl) as a Python module: taint.py

The following is my first attempt at adding a taint feature to Python
to prevent os.system() from being called with untrusted input. What do
you think of it?

# taint.py - Emulate Perl's taint feature in Python
# Copyright (C) 2007 Johann C. Rocholl <jo****@rocholl.net>
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
"""
Emulate Perl's taint feature in Python

This module replaces all functions in the os module (except stat) with
wrappers that will raise an Exception called TaintError if any of the
parameters is a tainted string.

All strings are tainted by default, and you have to call untaint on a
string to create a safe string from it.

Stripping, zero-filling, and changes to lowercase or uppercase don't
taint a safe string.

If you combine strings with + or join or replace, the result will be a
tainted string unless all its parts are safe.

It is probably a good idea to run some checks on user input before you
call untaint() on it. The safest way is to design a regex that matches
legal input only. A regex that tries to match illegal input is very
hard to prove complete.

You can run the following examples with the command
python taint.py -v
to test if this module works as designed.
>>unsafe = 'test'
tainted(unsafe)
True
>>os.system(unsafe)
Traceback (most recent call last):
TaintError
>>safe = untaint(unsafe)
tainted(safe)
False
>>os.system(safe)
256
>>safe + unsafe
u'testtest'
>>safe.join([safe, unsafe])
u'testtesttest'
>>tainted(safe + unsafe)
True
>>tainted(safe + safe)
False
>>tainted(unsafe.join([safe, safe]))
True
>>tainted(safe.join([safe, unsafe]))
True
>>tainted(safe.join([safe, safe]))
False
>>tainted(safe.replace(safe, unsafe))
True
>>tainted(safe.replace(safe, safe))
False
>>tainted(safe.capitalize()) or tainted(safe.title())
False
>>tainted(safe.lower()) or tainted(safe.upper())
False
>>tainted(safe.strip()) or tainted(safe.rstrip()) or tainted(safe.lstrip())
False
>>tainted(safe.zfill(8))
False
>>tainted(safe.expandtabs())
True
"""

import os
import types
class TaintError(Exception):
"""
This exception is raised when you try to call a function in the os
module with a string parameter that isn't a SafeString.
"""
pass
class SafeString(unicode):
"""
A string class that you must use for parameters to functions in
the os module.
"""

def __add__(self, other):
"""Create a safe string if the other string is also safe."""
if tainted(other):
return unicode.__add__(self, other)
return untaint(unicode.__add__(self, other))

def join(self, sequence):
"""Create a safe string if all components are safe."""
for element in sequence:
if tainted(element):
return unicode.join(self, sequence)
return untaint(unicode.join(self, sequence))

def replace(self, old, new, *args):
"""Create a safe string if the replacement text is also
safe."""
if tainted(new):
return unicode.replace(self, old, new, *args)
return untaint(unicode.replace(self, old, new, *args))

def strip(self, *args):
return untaint(unicode.strip(self, *args))

def lstrip(self, *args):
return untaint(unicode.lstrip(self, *args))

def rstrip(self, *args):
return untaint(unicode.rstrip(self, *args))

def zfill(self, *args):
return untaint(unicode.zfill(self, *args))

def capitalize(self):
return untaint(unicode.capitalize(self))

def title(self):
return untaint(unicode.title(self))

def lower(self):
return untaint(unicode.lower(self))

def upper(self):
return untaint(unicode.upper(self))
# Alias to the constructor of SafeString,
# so that untaint('abc') gives you a safe string.
untaint = SafeString
def tainted(param):
"""
Check if a string is tainted.
If param is a sequence or dict, all elements will be checked.
"""
if isinstance(param, (tuple, list)):
for element in param:
if tainted(element):
return True
elif isinstance(param, dict):
return tainted(param.values())
elif isinstance(param, (str, unicode)):
return not isinstance(param, SafeString)
else:
return False
def wrapper(function):
"""Create a new function that checks its parameters first."""
def check_first(*args, **kwargs):
"""Check all parameters for unsafe strings, then call."""
if tainted(args) or tainted(kwargs):
raise TaintError
return function(*args, **kwargs)
return check_first
def install_wrappers(module, innocent):
"""
Replace each function in the os module with a wrapper that checks
the parameters first, except if the name of the function is in the
innocent list.
"""
for name, function in module.__dict__.iteritems():
if name in innocent:
continue
if type(function) in [types.FunctionType,
types.BuiltinFunctionType]:
module.__dict__[name] = wrapper(function)
install_wrappers(os, innocent = ['stat'])
if __name__ == '__main__':
import doctest
doctest.testmod()

Feb 5 '07 #1
5 2136
En Mon, 05 Feb 2007 19:13:04 -0300, Johann C. Rocholl
<jc*******@googlemail.comescribió:
The following is my first attempt at adding a taint feature to Python
to prevent os.system() from being called with untrusted input. What do
you think of it?
A simple reload(os) will drop all your wrapped functions, leaving the
original ones.
I suppose you don't intend to publish the SafeString class - but if anyone
can get a SafeString instance in any way or another, he can convert
*anything* into a SafeString trivially.
And tainted() returns False by default?????

Sorry but in general, this won't work :(

--
Gabriel Genellina

Feb 6 '07 #2
"Gabriel Genellina" <ga******@yahoo.com.arwrites:
I suppose you don't intend to publish the SafeString class - but if
anyone can get a SafeString instance in any way or another, he can
convert *anything* into a SafeString trivially.
The point (in Perl) of detecting taint isn't to prevent a programmer
from deliberately removing the taint. It's to help the programmer find
places in the code where taint accidentally remains.
And tainted() returns False by default?????
Sorry but in general, this won't work :(
I'm inclined to agree that the default should be to flag an object as
tainted unless known otherwise.

--
\ "On the other hand, you have different fingers." -- Steven |
`\ Wright |
_o__) |
Ben Finney

Feb 6 '07 #3
En Mon, 05 Feb 2007 23:01:51 -0300, Ben Finney
<bi****************@benfinney.id.auescribió:
"Gabriel Genellina" <ga******@yahoo.com.arwrites:
>I suppose you don't intend to publish the SafeString class - but if
anyone can get a SafeString instance in any way or another, he can
convert *anything* into a SafeString trivially.

The point (in Perl) of detecting taint isn't to prevent a programmer
from deliberately removing the taint. It's to help the programmer find
places in the code where taint accidentally remains.
I'm not convinced at all of the usefulness of tainting.
How do you "untaint" a string? By checking some conditions?
Let's say, you validate and untaint a string, regarding it's future usage
on a command line, so you assume it's safe to use on os.system calls - but
perhaps it still contains a sql injection trap (and being untainted you
use it anyway!).
Tainting may be useful for a short lived string, one that is used on the
*same* process as it was created. And in this case, unit testing may be a
good way to validate the string usage along the program.
But if you store input text on a database or configuration file (username,
password, address...) it may get used again by *another* process, maybe a
*different* program, even months later. What to do? Validate all input for
any possible type of unsafe usage before storing them in the database, so
it is untainted? Maybe... but I'd say it's better to ensure things are
*done* *safely* instead of trusting a flag. (Uhmm, perhaps it's like "have
safe sex; use a condom" instead of "require an HIV certificate")

That is:
- for sql injection, use parametrized queries, don't build SQL statements
by hand.
- for html output, use any safe template engine, always quoting inputs.
- for os.system and similar, validate the command line and arguments right
before being executed.
and so on.

--
Gabriel Genellina

Feb 6 '07 #4
"Gabriel Genellina" <ga******@yahoo.com.arwrites:
I'm not convinced at all of the usefulness of tainting.
How do you "untaint" a string? By checking some conditions?
In perl? I don't think you can untaint a string, but you can make a
new untainted string by extracting a regexp match from the tainted
string's contents.
Let's say, you validate and untaint a string, regarding it's future
usage on a command line, so you assume it's safe to use on os.system
calls - but perhaps it still contains a sql injection trap (and being
untainted you use it anyway!).
Well, ok, you didn't check it carefully enough, but at least you made
an attempt. Taint checking is a useful feature in perl.
Tainting may be useful for a short lived string, one that is used on
the *same* process as it was created. And in this case, unit testing
may be a good way to validate the string usage along the program.
Unit testing is completely overrated for security testing. It checks
the paths through the program that you've written tests for. Taint
checking catches errors in paths that you never realized existed.
- for sql injection, use parametrized queries, don't build SQL
statements by hand.
- for html output, use any safe template engine, always quoting inputs.
- for os.system and similar, validate the command line and arguments
right before being executed. and so on.
Right, but it's easy to make errors and overlook things, and taint
checking catches a lot of such mistakes.
Feb 6 '07 #5
On Feb 6, 3:01 am, Ben Finney <bignose+hates-s...@benfinney.id.au>
wrote:
"Gabriel Genellina" <gagsl...@yahoo.com.arwrites:
And tainted() returns False by default?????
Sorry but in general, this won't work :(

I'm inclined to agree that the default should be to flag an object as
tainted unless known otherwise.
That's true. For example, my first attempt didn't prevent this:
os.open(buffer('/etc/passwd'), os.O_RDONLY)

Here's a stricter version:

def tainted(param):
"""
Check if a parameter is tainted. If it's a sequence or dict, all
values will be checked (but not the keys).
"""
if isinstance(param, unicode):
return not isinstance(param, SafeString)
elif isinstance(param, (bool, int, long, float, complex, file)):
return False
elif isinstance(param, (tuple, list)):
for element in param:
if tainted(element):
return True
elif isinstance(param, dict):
return tainted(param.values())
else:
return True

Feb 6 '07 #6

This thread has been closed and replies have been disabled. Please start a new discussion.

Similar topics

2
by: leegold2 | last post by:
Hi, I Perl there's that -t option that's supposed to check input for anything nasty. I wondered if there's anything link that in PHP? Some module? Or some tested block of PHP code that will do it?...
4
by: Ravi | last post by:
Hi, I did some googling, and found that there doesn't seem to be a pure python MySQL communication module. There is one for perl however, Net::MySQL. I was wondering if there was a specific...
0
by: Xah Lee | last post by:
© # -*- coding: utf-8 -*- © # Python © © # once a module is loaded © # import mymodule © # one can find all the names it © # export with dir() © © import sys © print dir(sys)
4
by: Xah Lee | last post by:
# -*- coding: utf-8 -*- # Python # some venture into standard modules import os # print all names exported by the module print dir(os)
9
by: Xah Lee | last post by:
# -*- coding: utf-8 -*- # Python # Matching string patterns # # Sometimes you want to know if a string is of # particular pattern. Let's say in your website # you have converted all images...
2
by: Xah Lee | last post by:
# -*- coding: utf-8 -*- # Python # suppose you want to fetch a webpage. from urllib import urlopen print urlopen('http://xahlee.org/Periodic_dosage_dir/_p2/russell-lecture.html').read() #...
4
by: Xah Lee | last post by:
20050207 text pattern matching # -*- coding: utf-8 -*- # Python # suppose you want to replace all strings of the form # <img src="some.gif" width="30" height="20"> # to # <img...
0
by: Kristina Clair | last post by:
Hi, I have a perl script running suid root (thus running in taint mode), and I'm trying to execute a shell command. Usually I do this using backticks so I can get the output, and usually it is...
0
by: Reedick, Andrew | last post by:
<snip> I have a Perl background and have found the O'Reilly books to be useful. The Learning Python book (or whatever it's called) is good because it covers the paradigm shifts and potential...
0
by: ryjfgjl | last post by:
ExcelToDatabase: batch import excel into database automatically...
0
by: Vimpel783 | last post by:
Hello! Guys, I found this code on the Internet, but I need to modify it a little. It works well, the problem is this: Data is sent from only one cell, in this case B5, but it is necessary that data...
0
by: jfyes | last post by:
As a hardware engineer, after seeing that CEIWEI recently released a new tool for Modbus RTU Over TCP/UDP filtering and monitoring, I actively went to its official website to take a look. It turned...
0
by: ArrayDB | last post by:
The error message I've encountered is; ERROR:root:Error generating model response: exception: access violation writing 0x0000000000005140, which seems to be indicative of an access violation...
1
by: PapaRatzi | last post by:
Hello, I am teaching myself MS Access forms design and Visual Basic. I've created a table to capture a list of Top 30 singles and forms to capture new entries. The final step is a form (unbound)...
1
by: Defcon1945 | last post by:
I'm trying to learn Python using Pycharm but import shutil doesn't work
1
by: Shællîpôpï 09 | last post by:
If u are using a keypad phone, how do u turn on JavaScript, to access features like WhatsApp, Facebook, Instagram....
0
by: af34tf | last post by:
Hi Guys, I have a domain whose name is BytesLimited.com, and I want to sell it. Does anyone know about platforms that allow me to list my domain in auction for free. Thank you
0
isladogs
by: isladogs | last post by:
The next Access Europe User Group meeting will be on Wednesday 3 Apr 2024 starting at 18:00 UK time (6PM UTC+1) and finishing by 19:30 (7.30PM). In this session, we are pleased to welcome former...

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.