本文介绍了执行CGI脚本时出现软件错误的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个上传的cgi脚本,如下所示:

 #!/ usr / bin / perl 
使用CGI;
使用CGI :: Carp qw(fatalsToBrowser);

my $ cgi = new CGI;
my $ file = $ cgi-> param('file');
$ file =〜m /^.*(\\ | \ /)(.*)/; #剥离远程路径并保留文件名
my $ name = $ 2;
打开(LOCAL,> / home / Desktop / $ name)或者死掉$ !; $(< $ file>){
$ data。= $ _;

;
}
print $ cgi-> header();
print$ file已成功上传......谢谢。
print $ data;

HTML文件如下

 < HTML> 
< head>
< title>测试< /标题>
< / head>

< body>
< form enctype =multipart / form-dataaction =upload.cgimethod =post>
< input type =hiddenname =MAX_FILE_SIZEvalue =30000/>
发送此文件:< input name =userfiletype =file/>
< input type =submitvalue =发送文件/>
< / form>
< / body>
< / html>

现在我收到了一个奇怪的错误..



软件错误:

是位于htdocs / upload.cgi第9行的目录。



,请发送邮件给本网站的网站管理员,提供此错误消息以及错误的时间和日期。

解决方案

TL; DR



strong>立即搁置您的脚本这是一个只是在等待被利用的安全漏洞。阅读以下资源:




  • (特别是) //www.owasp.org/index.php/Unrestricted_File_Upload 相对= nofollow>无限制文件上传


  • CWE页面

  • SANS推荐。



当您阅读并理解了所有这些内容时,并认为如果你真的需要让用户上传文件到你的服务器上。认为难。您是否真的可以解释所有列出的漏洞?如果您仍然觉得您需要这样做,请考虑获得安全专家的帮助。请仔细按照上述资源中列出的准则 ,并明白设计中的错误可能会危及整个网站的安全。

据我所知,这只是一个测试脚本,而不是生产应用程序(至少,我真的希望是这种情况),但即便如此,你在做什么(特别是你是这样做的)是一个非常不好的主意。这是
的选择少数的原因,从OWASP对:




  • 网站可能遭到破坏。

  • 可以通过上传和执行web-shell来破坏Web服务器,该Web-shell可以:运行命令,浏览系统文件,浏览本地资源,攻击其他服务器以及利用本地漏洞等。

  • 此漏洞可能会使网站容易受到攻击o其他类型的攻击,例如XSS。

  • 本地文件包含漏洞可以通过将恶意文件上传到服务器来利用。 b
    $ b

    来自OWASP的更多内容: b
    $ b

    很吓人的东西,呵呵?



    问题



    您的代码



    在您发布的代码的一些问题。



    没有严格,没有警告



    开始放置使用strict;使用警告; 位于每个Perl脚本的顶部,您
    曾经写过。
    我最近很高兴修复一个包含
    a片段的CGI脚本像这样:

      my($ match)= grep {/ $ usrname /} @users; 

    此代码用于检查在HTML表单中输入的用户名是否与
    列表匹配的有效用户。一个问题:变量 $ usrname
    拼写错误(它应该是 $ username )。由于严格的
    检查已关闭,Perl愉快地插入了(未声明的)全局
    变量 $ usrname undef 。这使得无辜的代码片段变成了这个怪物:

      my($ match)= grep {//} @users; 

    在有效用户列表中匹配 everything ,并返回第一个
    匹配。你可以输入任何你想要的形式
    的用户名字段,脚本会认为你是一个有效的用户。由于警告也被关闭,所以在开发过程中从未发现过
    。当你打开警告时,
    脚本仍然会运行并返回一个用户,但你也会得到类似
    这样的东西:

     名称main :: usrname仅用于一次:在-e行1可能出现错字。
    在-e行1中使用正则表达式编译中的未初始化值$ usrname。

    当您打开strict时,脚本无法编译,甚至不会在
    all运行。这段代码还有其他问题(例如,字符串'a'将与用户名'janedoe'相匹配),但严格和警告至少提醒我们一个主要问题。我无法强调这一点:始终总是 use strict;使用
    警告;



    无污染模式



    第一条规则的网络开发是
    始终清理用户输入。重复一遍:始终清理用户输入。还有一次:总是对用户输入进行清理。
    换句话说,从来没有
    盲目信任用户输入而不先验证它。用户(即使是非恶意的用户)也非常擅长将创意值输入到可能会破坏应用程序(或更糟糕)的
    字段中。如果您不限制他们的创造力,
    对恶意用户可能对您的网站造成的损害没有限制(请参阅中,常年#1
    漏洞)。

    Perl的污点模式可以帮助解决这个问题。在
    system()函数等特定潜在危险操作中使用它之前,污染模式会强制您
    检查所有用户输入。污染模式就像枪支上的安全:它可以防止许多令人痛苦的
    事故(尽管如果你真的想在脚下拍自己,你可以总是关掉安全,就像你一样修改变量而不实际删除危险字符)。
    在你编写的每个CGI脚本的中打开污点模式。可以通过传递 -T 标志来启用它,如下所示:

     #!/ usr / bin / perl -T 

    一旦启用了污染模式,如果您尝试在危险情况下使用受污染的数据
    ,则脚本将引发致命错误。以下是我在互联网上随机脚本中发现的这种危险情况的示例:

      open(LOCAL,> / home / Desktop / $ name)或者死掉$ !; 

    好的,我说谎了,该片段不是来自随机脚本,而是来自您的代码。孤立地说,这段代码只是乞求遭受,恶意用户输入一个相对路径以访问他们不应该访问的文件。



    幸运的是,您已经在这里做了一些事情:确保 $ name 将不包含使用regex 的目录分隔符。这正是污点模式需要你做的事情。污染模式的好处是,如果您忘记清理输入内容,您将立即收到错误信息:

     在foo.cgi中使用-T开关运行时,open-while时出现不安全依赖关系第5行

    强迫你通过导致程序失败而立即解决你的代码中的问题,而不是让它安静地徘徊。



    *你做了一些 em>正确,但你也做了一些错误的事情:

    只有一个没有目录分隔符的文件名,例如 foo

  • 您不会删除shell可解释的特殊字符,如 |
  • code>
  • 您永远不会清理变量 $ file ,但您可以稍后尝试使用它读取文件code

  • 您不检查您写入的文件是否已存在(请参阅下面的不检查文件存在情况)
  • 您允许用户选择将存储在服务器上的文件的名称,这使它们的控制远远超出您应该理解的范围(请参阅下面的允许用户设置文件名)。


CGI :: Carp fatalsToBrowser



我会给你这个疑问的好处因为您仍在测试您的脚本,但为防万一您不知道,并且由于我已经在讨论CGI安全问题,请勿在生产环境中启用CGI :: Carp的fatalsToBrowser选项。它可以揭示关于这件事的私密细节
$ b

双参数 open()和全局文件句柄



双参数 open(),例如

 打开FH,> $文件

有一个当用户被允许指定文件路径时。您的脚本通过使用硬编码的目录前缀来缓解其中的许多问题,但这绝不会减少使用双参数open会导致非常危险的事实。一般而言,您应该使用three-参数表:

 打开我的$ fh,>,$ file 

(如果允许用户指定文件名,这仍然很危险;请参阅下面的允许用户设置文件名)



另请注意,替换全局文件句柄 FH ,我切换到了一个词法文件句柄 $ fh 。请参见CERT的网页。):

  $ lightweight_fh = $ q-> upload('field_name'); 
#undef可能被返回,如果它不是一个有效的文件句柄
if(defined $ lightweight_fh){
#将句柄升级到与IO :: Handle兼容的句柄:
my $ io_handle = $ lightweight_fh->句柄;
open(OUTFILE,'>>','/ usr / local / web / users / feedback');
while($ bytesread = $ io_handle-> read($ buffer,1024)){
print OUTFILE $ buffer;


其中 field_name 是保存文件名的POST变量的名称(在你的情况下, userfile )。请注意,示例代码不会根据用户输入设置输出文件名,这会导致我的下一个问题。



允许用户设置文件名



永不 允许用户选择将在您的服务器上使用的文件名。如果攻击者可以将恶意文件上传到已知位置,则攻击者可以更容易利用该文件。相反,生成一个新的,唯一的(防止破坏),难以猜测的文件名,最好在web根目录之外的路径中,以便用户无法直接通过URL访问它们。



其他问题



您甚至没有开始来解决以下问题。



身份验证



允许谁使用您的网络应用上传文件?你将如何确保只有授权用户才能上传文件?



访问控制



用户是否可以看到其他用户上传的文件?根据文件内容,可能存在重大隐私问题。



上传数量和速度



一位用户允许上传多少个文件?用户允许在固定的时间段内上传多少个文件?如果您不限制这些内容,即使强制执行最大文件大小,一个用户也可以非常快速地轻松占用所有服务器资源。



危险文件类型



您如何检查用户是否未将危险内容(例如可执行的PHP代码)上传到您的服务器?仅仅检查文件扩展名或内容类型标题是不够的;攻击者已经发现了一些非常有创意的方法来规避这种检查。

但是,我只在我的公司内部网上运行这个...



如果您的脚本无法从互联网访问,您可能会忽视这些安全问题。但是,您仍然需要考虑


  • 办公室恶作剧

  • 不满的同事

  • 需要访问您的应用或者无法访问的合作者和外部合同商

  • 经理热爱您的应用,决定打开它在您不知情的情况下向互联网上的用户发送信息,可能在您转移到另一个群组或离开公司后


  • 废弃现有的代码。仔细阅读我在第一段中列出的资源。在这里,他们又是:





    • (特别是关于)

    • OWASP的

    • 资讯安全的

    • CWE的 resecurity.sans.org/blog/2009/12/28/8-basic-rules-to-implement-secure-file-uploadsrel =nofollow> 8实施安全文件上传的基本规则



    请仔细考虑您是否真的需要这样做。如果你只需要给用户一个存放文件的地方,可以考虑使用(S)FTP。这肯定不会消除所有的安全风险,但它会消除一个很大的问题:您的自定义CGI代码。



    如果经过仔细考虑,您仍然认为这是必要的,通过一些,确保您可以使用和理解现代Perl编程约定。使用像这样的框架,而不是CGI.pm,或,所有这些插件都可以处理棘手的问题如用户认证和会话,所以你不必重新发明轮子(很差)。

    请遵循上述资源中列出的所有安全指导原则,并考虑争取网络安全专家的帮助。仔细阅读:代码中的单个错误可能会导致攻击者损害整个网站,甚至可能导致网络上的其他计算机受到危害。根据您的公司和用户所在的国家/地区,这甚至可能会产生法律后果。 $ b >


    I have a cgi script for upload which is as follows

    #!/usr/bin/perl
    use CGI;
    use CGI::Carp qw(fatalsToBrowser);
    
    my $cgi = new CGI;
    my $file = $cgi->param('file');
    $file=~m/^.*(\\|\/)(.*)/; # strip the remote path and keep the filename
    my $name = $2;
    open(LOCAL, ">/home/Desktop/$name") or die $!;
    
    while(<$file>) {
      $data .= $_;
    }
    print $cgi->header();
    print "$file has been successfully uploaded... thank you.\n";
        print $data;
    

    The HTML file is as follows

    <html>
    <head>
        <title>Test</title>
    </head>
    
    <body>
        <form enctype="multipart/form-data" action="upload.cgi" method="post">
        <input type="hidden" name="MAX_FILE_SIZE" value="30000" />
                Send this file: <input name="userfile" type="file" />
    <input type="submit" value="Send File" />
    </form>
    </body>
    </html>
    

    I am getting a weird error now..

    Software error:

    Is a directory at htdocs/upload.cgi line 9.

    For help, please send mail to this site's webmaster, giving this error message and the time and date of the error.

    解决方案

    TL;DR

    Stop. Shelve your script right now. It is a gaping security hole just waiting to be exploited. Read the following resources:

    When you have read--and understood--all of them, stop and think if you really need to let users upload files onto your server. Think long and hard. Can you really account for all of the listed vulnerabilities? If you still feel like you need to do this, consider enlisting the help of a security expert. Follow the guidelines laid out in the above resources carefully and understand that a mistake in your design could compromise your entire site.


    I understand that this is just a test script, not a production application (at least, I really hope that's the case), but even so, what you are doing (and particularly how you are doing it) is a very, very bad idea. Here are a select few ofthe reasons why, from OWASP's page on Unrestricted FileUpload:

    • The website can be defaced.
    • The web server can be compromised by uploading and executing a web-shell which can: run a command, browse the system files, browse the local resources, attack to other servers, and exploit the local vulnerabilities, and so on.
    • This vulnerability can make the website vulnerable to some other types of attacks such as XSS.
    • Local file inclusion vulnerabilities can be exploited by uploading a malicious file into the server.

    More from OWASP:

    Pretty scary stuff, huh?

    The problems

    Your code

    Let's start by looking at some of the problems with the code you posted.

    No strict, no warnings

    Start putting use strict; use warnings; at the top of every Perl script youever write. I recently had the pleasure of fixing a CGI script that containeda snippet something like this:

    my ($match) = grep { /$usrname/ } @users;
    

    This code was used to check that the username entered in an HTML form matched alist of valid users. One problem: the variable $usrname wasmisspelled (it should have been $username with an 'e'). Since strictchecking was off, Perl happily inserted the value of the (undeclared) globalvariable $usrname, or undef. That turned the innocent-looking snippet into this monstrosity:

    my ($match) = grep { // } @users;
    

    which matches everything in the valid users list and returns the firstmatch. You could enter anything you wanted into the username field in the formand the script would think you were a valid user. Since warnings were also off,this was never caught during the development process. When you turn warnings on,the script will still run and return a user, but you also get something likethis:

    Name "main::usrname" used only once: possible typo at -e line 1.
    Use of uninitialized value $usrname in regexp compilation at -e line 1.
    

    When you also turn on strict, the script fails to compile and won't even run atall. There are other problems with this snippet (for example, the string 'a' will match the username 'janedoe'), but strict and warnings at least alerted us to one major issue. I cannot stress this enough: always, always use strict; usewarnings;

    No taint mode

    The first rule of web development is,"Always sanitize user input." Repeat after me: Always sanitize user input. One more time: Always sanitize user input. In other words, neverblindly trust user input without validating it first. Users (even those that are not malicious) are very good at entering creative values into formfields that can break your application (or worse). If you don't restrict their creativity,there is no limit to the damage a malicious user can do to your site (refer to the perennial #1vulnerability on the OWASP Top 10,injection).

    Perl's taint mode can help with this. Taint mode forces youto check all user input before using it in certain potentially dangerous operations like thesystem() function. Taint mode is like the safety on a gun: it can prevent a lot of painfulaccidents (although if you really want to shoot yourself in the foot, you canalways turn off the safety, like when you untaint a variable without actually removing dangerous characters).Turn on taint mode in every CGI script you ever write. You can enable it by passing the -T flag, like this:

    #!/usr/bin/perl -T
    

    Once taint mode is enabled, your script will throw a fatal error if you try touse tainted data in dangerous situations. Here's an example of such a dangerous situation that I found in a random script on the internet:

    open(LOCAL, ">/home/Desktop/$name") or die $!;
    

    Ok, I lied, that snippet isn't from a random script, it's from your code. In isolation, this snippet is just begging to be hit with a directory traversal attack, where a malicious user enters a relative path in order to access a file that they shouldn't have access to.

    Fortunately, you've done something right here: you ensured that $name will contain no directory separators by using a regex. This is exactly what taint mode would require you to do. The benefit of taint mode is that if you forget to sanitize your input, you will be alerted immediately with an error like this:

    Insecure dependency in open while running with -T switch at foo.cgi line 5
    

    Like strict, taint mode forces you to address problems in your code immediately by causing the program to fail, instead of allowing it to quietly limp along.

    * You did something right, but you also did some things wrong:

    • Your program will die if the user passes in only a filename with no directory separators, e.g. foo
    • You don't remove special characters that could be interpreted by a shell, like |
    • You never sanitize the variable $file and yet you try to use it to read a file later in your code
    • You don't check if the file you're writing to already exists (see "No check for file existence" below)
    • You allow the user to choose the name of the file that will be stored on your server, which gives them far more control than you should be comfortable with (see "Allowing the user to set the file name" below)

    CGI::Carp fatalsToBrowser

    I'll give you the benefit of the doubt on this one since you're still testing your script, but just in case you weren't aware and since I'm already talking about CGI security issues, never enable CGI::Carp's fatalsToBrowser option in a production environment. It can reveal intimate details about the inner workings of your script to attackers.

    Two-argument open() and global filehandles

    Two-argument open(), e.g.

    open FH, ">$file"
    

    has a host of security risks associated with it when users are allowed to specify the file path. Your script mitigates many of these by using a hard-coded directory prefix, but that in no way diminishes the fact that using two-argument open can be very dangerous. In general, you should use the three-argument form:

    open my $fh, ">", $file
    

    (which is still plenty dangerous if you allow the user to specify the file name; see "Allowing the user to set the file name" below).

    Also note that instead of the global filehandle FH I switched to a lexical filehandle $fh. See CERT's page Do not use bareword filehandles for some reasons why.

    No check for file existence

    You don't check whether a file already exists at /home/Desktop/$name when you open it for writing. If the file already exists, you will truncate it (erase its contents) as soon as the open() call succeeds, even if you never write anything to the file. Users (malicious and otherwise) are likely to clobber each other's files, which doesn't make for a very happy user base.

    No limit on file size

    "But wait," you say, "I set MAX_FILE_SIZE in my HTML form!" Understand that this is merely a suggestion to the browser; attackers can easily edit HTTP requests to remove this condition. Never rely on hidden HTML fields for security. Hidden fields are plainly visible in the HTML source of your page and in the raw HTTP requests. You must limit the maximum request size on the server side to prevent users from loading massive files to your server and to help alleviate one type of denial of service attack. Set the $CGI::POST_MAX variable at the beginning of your CGI script like this:

    $CGI::POST_MAX=1024 * 30;  # 30KB
    

    Or even better, find CGI.pm on your system and change the value of $POST_MAX to set it globally for all scripts that use the CGI module. That way you don't have to remember to set the variable at the beginning of every CGI script you write.

    CGI doesn't match the HTML form

    The POST variable you use for the file path in your HTML form, userfile, does not match the variable you look for in your CGI script, file. This is why your script is failing with the error

    Is a directory
    

    The value of

    $cgi->param('file')
    

    is undef so your script tries to open the path

    /home/Desktop/
    

    as a regular file.

    Obsolete method for handling upload

    You are using the old (and obsolete) method of handling uploads with CGI.pm where param() is used to get both the file name and a lightweight filehandle. This will not work with strict and is insecure. The upload() method was added in v2.47 (all the way back in 1999!) as a preferred replacement. Use it like this (straight out of the documentation for CGI.pm):

    $lightweight_fh  = $q->upload('field_name');
    # undef may be returned if it's not a valid file handle
    if (defined $lightweight_fh) {
        # Upgrade the handle to one compatible with IO::Handle:
        my $io_handle = $lightweight_fh->handle;
        open (OUTFILE,'>>','/usr/local/web/users/feedback');
        while ($bytesread = $io_handle->read($buffer,1024)) {
            print OUTFILE $buffer;
        }
    }
    

    where field_name is the name of the POST variable that holds the file name (in your case, userfile). Notice that the sample code does not set the output filename based on user input, which leads to my next point.

    Allowing the user to set the file name

    Never allow users to choose the file name that will be used on your server. If an attacker can upload a malicious file to a known location, it becomes significantly easier for them to exploit. Instead, generate a new, unique (to prevent clobbering), difficult-to-guess file name, preferably in a path outside your web root so users cannot access them directly with a URL.

    Other issues

    You haven't even begun to address the following issues.

    Authentication

    Who is allowed to upload files using your web app? How will you ensure that only authorized users are uploading files?

    Access control

    Are users allowed to see the files uploaded by other users? Depending on the file content, there could be major privacy issues at stake.

    Number and rate of uploads

    How many files is one user allowed to upload? How many files is a user allowed to upload in a fixed period of time? If you don't restrict these, one user could easily eat up all of your server resources very quickly, even if you enforce a maximum file size.

    Dangerous file types

    How will you check that users are not uploading dangerous content (for example, executable PHP code) to your server? Simply checking the file extension or content type header is not enough; attackers have found some very creative methods for circumventing such checks.

    "But, but, I'm only running this on my corporate intranet..."

    You may be tempted to disregard these security issues if your script is not accessible from the internet. However, you still need to consider

    • In-office pranksters
    • Disgruntled coworkers
    • Collaborators and outside contractors who either need access to your app or who shouldn't have access
    • Managers who love your app so much that they decide to open it up to users on the internet without your knowledge, possibly after you've transferred to another group or left the company

    "What should I do?"

    Scrap your existing code. Read the resources I listed in the first paragraph, carefully. Here they are again:

    Consider carefully if you really need to do this. If you just need to give users a place to store files, consider using (S)FTP instead. This would certainly not eliminate all of the security risks, but it would eliminate a big one: your custom CGI code.

    If after careful consideration you still think this is necessary, work through some recent Perl tutorials to make sure you can use and understand modern Perl programming conventions. Instead of CGI.pm, use a framework like Catalyst, Dancer, or Mojolicious, all of which have plugins that can handle tricky areas like user authentication and sessions so you don't have to re-invent the wheel (poorly).

    Follow all of the security guidelines listed in the above resources and consider enlisting the help of an expert in web security. Tread carefully: a single mistake in your code could allow an attacker to compromise your entire site and possibly even other machines on your network. Depending on what country your company and your users are in, this could even have legal ramifications.

    </soapbox>

    这篇关于执行CGI脚本时出现软件错误的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-11 16:17