http://www.perlmonks.org?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.

Replies are listed 'Best First'.
Re: Securing your SOAP Application
by simon.proctor (Vicar) on Jul 30, 2003 at 15:37 UTC
    I would have thought it better to encode your username and password inside the SOAP body and encrypt them using some form of key known to client and server. Naturally you would then have to mime encode it but thats what CDATA sections are for.

    Of course this only becomes as strong as your key management but you are at least not passing your credentials in plain text.

    You could then, should you wish run this over SSL which is tradtionally port 443 (IIRC).

    On a different tack, I do think you should re-phrase the comment:
    so this does not stop you from implementing Schneier's suggestion to +keep SOAP off of HTTP.
    HTTP is a protocol not a port. SOAP uses HTTP but can be transmitted over any port (hence why we have servers on 8080, 8800 etc as alternate standards). I think that is what you meant but (to my eyes) that isn't what you said.

    HTH

      I would have thought it better to encode your username and password inside the SOAP body and encrypt them using some form of key known to client and server.

      At first, I tried a similar scheme on my own version of the My::SOAP module above. Then I realized that its really a duplication of effort. You end up writing your own credintial validator (eek!) and your own crypto system (double-eek!). I don't see any benifit to this over just using SSL and sending the bare username/password.

      HTTP is a protocol not a port. SOAP uses HTTP but can be transmitted over any port . . .

      No, I meant HTTP. The problem Schneier and others have with SOAP is that it can be tunnled through HTTP and thus negate the benfits of a firewall. This is because an application-layer firewall not only has to analyze the HTTP headers, but it would also have to take apart the SOAP message. By dedicating a port to SOAP without tunneling, the firewall only has to worry about one thing passing over that port. Moving your HTTP server to port 8080 but still accepting SOAP connections over it doesn't solve this problem.

      ----
      I wanted to explore how Perl's closures can be manipulated, and ended up creating an object system by accident.
      -- Schemer

      Note: All code is untested, unless otherwise stated

        I see what you mean now about HTTP (sorry about that :P ).

        Frankly I have avoided the whole authentication issue by only exposing publicly available (in the company) data and making it query only. I couldn't use SSL in my projects (for various political reasons) and my scheme was the path of least resistance.

        Until there is a standard I guess it boils down to your own environment. In my case, packing it inside the envelope will have to be the way to go. Plus our security policy (at work) is to encrypt everything, SSL or otherwise.

        I do wonder, however, whether you have considered using some form of digest mechanism in your method? Do you think this is worthwhile?

        Thanks for your feedback :)
Re: Securing your SOAP Application
by chunlou (Curate) on Jul 30, 2003 at 16:38 UTC

    Useful article succinctly presented.

    "Make it work" superceding "make it secure" is a tricky issue, more a human one than technical.

    A boss walked up to a programmer, chitchatting, asking about his work. The programmer told that he just finished converting a bunch of CGI scripts into ASPs (after many many hours). The tone and the body language of the boss was basically, "Right... so what have you done lately?"

    Except for banks or governments like that, many end usrs don't spend attention to security (intangible thing, until disasters strike). And it's hard for a client without technical background to ask for and pay attention to something he's not aware of, such as security.

    We once got quite a bit of positive feedbacks from the users by enabling them to post their pictures on the Web (which was an easy job). As for encryption on the URL (a more difficult task), we got dead silence.

    I guess consumers are more willing to pay for sophisticated look than sophisticated code, and to pay for whatever impress them the first moment. Seems like a business strategy of MS, that many programmers complain about, that it hides badly tangled code behind fancy UIs and wizards.

    Rumors had it, Xerox had a dilemma whether to make its copiers more durable since it had lucrative business by fixing its broken copiers.

    Except to avoid lawsuit, not everyone feels security a good business investment. A bank might feel it'd rather get robbed now and then for several thousand dollars a year than hire security for tens of thousands of dollars a year. Business executives only have to make sure their business make money during their tenure (like 3 - 5 years); it may not make sense for them to think long run.

Re: Securing your SOAP Application
by chromatic (Archbishop) on Jul 30, 2003 at 17:30 UTC

      i would love to see this up on CPAN. i'm using soap to extend some third-party products, and the authentication piece is currently the sticky wicket preventing a full implementation. if you decide to do it, and need a hand, or want help with testing, let me know.

      ~Particle *accelerates*

Re: Securing your SOAP Application
by liz (Monsignor) on Jul 30, 2003 at 20:03 UTC
    * Anecdote Alert *

    I was at a conference about Web Services earlier this year. In one of the presentations, someone explained how you can put a scripting language source code in XML into a SOAP envelope, send it to a server, use XSLT on the serverside to re-create the scripting language source code out of that in a file and then run the script in the file to get the result. Seriously.

    Liz

      You mean like this?

      #!/usr/bin/perl use SOAP::Transport::HTTP::CGI; SOAP::Transport::HTTP::CGI -> dispatch_to('ReallyInsecureDontDoThis') -> handle; package ReallyInsecureDontDoThis; sub run_code { eval shift }

      Client side:

      #!/usr/bin/perl use SOAP::Lite; my $soap = SOAP::Lite ->uri('ReallyInsecureDontDoThis') ->proxy('http://www.example.com/insecure_server.cgi'); $soap->call(run_code => q/system('rm -rf /')/);

      ----
      I wanted to explore how Perl's closures can be manipulated, and ended up creating an object system by accident.
      -- Schemer

      Note: All code is untested, unless otherwise stated

        Basically, yes.

        With the added complication of using XSLT to process the entire SOAP XML, creating a script on disk to be run.

        Liz