http://qs321.pair.com?node_id=279201

As bad a reputation as CGI has gotten, at least it has some years behind it so that any major security problems can be blamed on flat-out ignorance rather than being merely naive. ("Let's hook /bin/sh directly into the web server so that people can do www.example.com/sh?rm+-rf+.! Wheeee!")

CGI was a first step into executing code on a web server, as oppsed to just delivering content. Parameters are passed either through the URI (in the case of GET) or through the data in a POST. The CGI then returns content on its STDOUT, which is piped by the web server back to the client. The next step was to make it possible to directly call subroutines on the web server as if they were being done locally, which is the dream of SOAP.

Uh oh.

SOAP's biggest security problem also happens to be its biggest selling point (especially by Microsoft): it is easy to setup an existing application to become a web application. Easy as in "make it work", not "make it secure". One of its most contriversial points is that it tunnels itself through HTTP, bypassing those pesky firewalls that were setup to help security in the first place. Bruce Schneier has suggested (external link) that future versions of SOAP be forbidden entirely from working over HTTP, instead using a seperate TCP port like most other RPC protocols out there.

Debating the merits of tunneling over HTTP or not isn't the point of this article, and I only mention it for the sake of completeness. It has been debated elsewhere, and IMHO, it is actually detracting from the more relevent discussion on how to make your SOAP application secure. Such discussion is important no matter if you are running your app over port 80 or not.

Rather, I want to demonstrate some simple ways to make an existing Perl module secure (though you should be able to generalize it to any language). As is always the case, security is not a checklist item. You can do everything in this article and still not be secure. I hope, however, that this can at least get a discussion going on the merits of the suggestions here, as well as other ways to help your security.

We'll start with a CGI-based SOAP server:

#!/usr/bin/perl use SOAP::Transport::HTTP; SOAP::Transport::HTTP::CGI -> dispatch_to('/home/soap/modules') -> handle;

Note the dispatch_to line. Only Perl modules in that directory can be executed, which is implemented by modifying @INC to contain only the directories specified when your client asks to use a module. @INC is later set back to its orginal list, so your SOAP modules can then use any other installed module.

Let's make a simple "Hello, World!" module, using a blessed referance to more accurately model a real application:

package Hello; use strict; sub new { my $class = shift; my $self = shift || { }; bless $self, $class; } sub hello { my $self = shift; return 'Hello, World!' } 1;

Save this and install it as a regular Perl module. This gets us the basic functionality for our application. There are no obvious security holes for the above, even if we made it directly available from SOAP. However, the rest of this article assumes that we are really doing a much more complex application that we want to stop the Bad Guys from feeding bad data.

With that in mind, we make another module that creates a front-end to Hello.pm:

package SOAPHello; use strict; use Hello; my $hi; sub new { my $class = shift; my $self = { }; $hi = Hello->new($self); bless $self, $class; } sub hello { return $hi->hello; } 1;

In a real world, we would grab input parameters and validate them, but for now I want to keep things simple.

Ignoring the validation issue, the above class is merely a thin layer between our module and the client. It ammounts to nothing more than indirection, and there is no more security here than if we had made the Hello.pm module directly available.

One problem SOAP doesn't address directly is authenticating users. If you ask, you'll be told to use traditional HTTP authentication on your web server, perhaps combined with SSL. The problem here is that it isn't easy to make some modules accessable to everyone and others accessable to only a few. In other words, we need a group model. If there was an easy way for our SOAP application to get at the HTTP headers, we wouldn't have this problem, but there isn't (at least, I haven't seen one yet).

Instead, we can make the username and password parameters to our interface modules. We can accomplish this with the help of Apache::Htpasswd for checking against a .htpasswd password file, and Set::NestedGroups for checking that a user has access to this group. See the documentation for those modules for how to add and remove users.

package My::SOAP; use strict; # These subroutines define constants that can be # overridden in a subclass. Most subclasses only # need to change ACL_GROUP(). # sub PASSWD_FILE { '/home/soap/.htpasswd' } sub ACL_FILE { '/home/soap/.acl' } sub ACL_GROUP { '' } # Authenticate the user, checking their password and # if they have permission to access this. Uses class data # $self->{user} and $self->{passwd} for checking. # # Return values: # # 1 Everything checks out # 0 Password is wrong # -1 Not allowed access # sub _authen { my $self = shift; use Apache::Htpasswd; my $htpasswd = Apache::Htpasswd->new({ passwdFile => $self->PASSWD_FILE, ReadOnly => 1, }); return 0 unless $htpasswd->htCheckPassword($self->{user}, $self->{ +passwd}); use Set::NestedGroups; open(ACL, '<', $self->ACL_FILE) or die "Can't open " . $self->ACL_FILE . ": $!\n"; my $acl = Set::NestedGroups->new(*ACL); close(ACL); return -1 unless $acl->member($self->{user}, $self->ACL_GROUP); return 1; } 1;

Install the above as a normal module.Then we make a few changes to SOAPHello.pm:

package SOAPHello; use strict; use base 'My::SOAP'; use Hello; # So we can use SOAP::Fault to generate error # messages for the client. use SOAP::Lite; # Make it so only users in the 'hello' group can access # this module. # sub ACL_GROUP { 'hello' } my $hi; sub new { my $class = shift; my $self = { }; my $self->{user} = shift || undef; my $self->{passwd} = shift || undef; $hi = Hello->new($self); bless $self, $class; my $self->_authen; if($auth == 0) { die SOAP::Fault ->faultcode('Server.BadAuthData') ->faultstring('The supplied username or password was incor +rect'); } elsif($auth <= 0) { die SOAP::Fault ->faultcode('Server.NoAccess') ->faultstring('You do not have access to this SOAP object' +); } return $self; } sub hello { return $hi->hello; } 1;

We now have a basic user/group model for keeping people out. A module simply defines what group it's a part of by overriding ACL_GROUP. Usernames and passwords can be encrypted by using SSL. Note that it is possible to setup the SOAP::Lite module so it can use any TCP port using SSL, so this does not stop you from implementing Schneier's suggestion to keep SOAP off of HTTP.

Again, the above doesn't necessarily make you secure. It's still possible for the Bad Guys to crack passwords by good ol' brute force. We haven't gone over parameter validation, which if not implemented in the underlieing module, should at least be put into our interface module.

The above illustrates an important point of securing a SOAP module: keep your underlieing module seperate from the outside world through a provided interface. This allows you to give conditional access to a module's subroutines. Perhaps you want to provide access to the foo() subroutine, but not bar() or baz(). This is easy with the above--we simply leave those subroutines out of the interface, but leave them in the underlieing module.

Again, the above doesn't necessarily make you secure. But it should get you started on basic security for your SOAP application.