Menu

File Upload Question - Security Breach

Help
2009-04-27
2013-06-03
  • Kathy Burch

    Kathy Burch - 2009-04-27

    I have a form where people can upload photos of their work (artist application) and someone was able to upload a php file with an .exe file into the server via this option. Luckily I caught it and deleted it before I clicked on the link but I would really like to be able to limit the form to only allow .jpeg or .gif extensions, no .php or .exe files.  Is this possible?

     
    • TNTEverett

      TNTEverett - 2009-04-27

      Yes, I have done this before.  My example below.

      Part 1 (replace):
                  function validateField(fieldId, fieldBoxId, fieldType, required)
                  {
                      fieldBox = document.getElementById(fieldBoxId);
                      fieldObj = document.getElementById(fieldId);
                      if(fieldType == 'text'  ||  fieldType == 'textarea'  ||  fieldType == 'password'  ||  fieldType == 'file'  ||  fieldType == 'phone'  || fieldType == 'website')
                      {  
                          if(required == 1 && fieldObj.value == '')
                          {
                              fieldObj.setAttribute("class","mainFormError");
                              fieldObj.setAttribute("className","mainFormError");
                              fieldObj.focus();
                              return false;                  
                          }
                      }
                      else if(fieldType == 'menu'  || fieldType == 'country'  || fieldType == 'state')
                      {  
                          if(required == 1 && fieldObj.selectedIndex == 0)
                          {              
                              fieldObj.setAttribute("class","mainFormError");
                              fieldObj.setAttribute("className","mainFormError");
                              fieldObj.focus();
                              return false;                  
                          }
                      }
                      else if(fieldType == 'email')
                      {  
                          if((required == 1 && fieldObj.value=='')  ||  (fieldObj.value!=''  && !validate_email(fieldObj.value)))
                          {              
                              fieldObj.setAttribute("class","mainFormError");
                              fieldObj.setAttribute("className","mainFormError");
                              fieldObj.focus();
                              return false;                  
                          }
                      }
                      else if(((fieldType == 'image') && (fieldObj.value != '' )) || (required == 1))
                      {  
                          if(!validate_image(fieldObj.value))
                          {              
                              fieldObj.setAttribute("class","mainFormError");
                              fieldObj.setAttribute("className","mainFormError");
                              fieldObj.focus();
                              return false;                  
                          }
                      }
                  }

      Part 2 (add):

                  function validate_image(imageStr)
                  {      
                      parts=imageStr.split(".");
                      if ((parts[(parts.length -1)] == 'jpg') || (parts[(parts.length -1)] == 'bmp') || (parts[(parts.length -1)] == 'gif'))
                      {
                              fieldObj.setAttribute("class","mainForm");
                              fieldObj.setAttribute("className","mainForm");
                              fieldObj.focus();
                              return true;
                      }
                      else
                      {
                              fieldObj.setAttribute("class","mainFormError");
                              fieldObj.setAttribute("className","mainFormError");
                              fieldObj.focus();
                              return false;
                      }
                  }

      Part 3 (modify):
      <script type="text/javascript">
              <!--
                  function validatePage1()
                  {
                      retVal = true;
                      if (validateField('field_1','fieldBox_1','text',1) == false)
                       retVal=false;
                      if (validateField('field_2','fieldBox_2','text',1) == false)
                       retVal=false;
                      if (validateField('field_3','fieldBox_3','text',1) == false)
                       retVal=false;
                      if (validateField('field_4','fieldBox_4','email',1) == false)
                       retVal=false;
                      if (validateField('field_6','fieldBox_6','text',1) == false)
                       retVal=false;
                      if (validateField('field_8','fieldBox_8','text',0) == false)
                       retVal=false;
                      if (validateField('field_10','fieldBox_10','text',0) == false)
                       retVal=false;
                      if (validateField('field_10','fieldBox_12','text',0) == false)
                       retVal=false;
                      if (validateField('field_10','fieldBox_14','text',0) == false)
                       retVal=false;
                      if (validateField('field_10','fieldBox_16','text',0) == false)
                       retVal=false;
                      if(retVal == false)
                      {
                          alert('Please correct the errors.  Fields marked with an asterisk (*) are required');
                          return false;
                      }
                      if (validateField('field_5','fieldBox_5','image',1) == false)
                       retVal=false;
                      if (validateField('field_7','fieldBox_7','image',0) == false)
                       retVal=false;
                      if (validateField('field_9','fieldBox_9','image',0) == false)
                       retVal=false;
                      if (validateField('field_11','fieldBox_11','image',0) == false)
                       retVal=false;
                      if (validateField('field_11','fieldBox_13','image',0) == false)
                       retVal=false;
                      if (validateField('field_11','fieldBox_15','image',0) == false)
                       retVal=false;
                      if(retVal == false)
                      {
                          alert('Please correct the errors.  Image types supported are jpg, bmp, gif');
                          return false;
                      }
                      return retVal;
                  }
              //-->
            </script>

       
    • Kathy Burch

      Kathy Burch - 2009-04-27

      Hi ... thanks for the response but I'm not technical enough to figure out what to do with all that. Our site was already hacked by the Islamic Ghost via another one of these files that was uploaded. Even though I deleted it, it replaced our home page with their hacking page. How can I prevent this? In very simple terms, please.  This is the code where the upload box is located:

      <li class="mainForm" id="fieldBox_14">
                          <label class="formFieldQuestion">Photo of your work&nbsp;<a class=info href=#><img src=imgs/tip_small.png border=0><span class=infobox>Please include a photo of your work if available</span></a></label><input class=mainForm type=file name=field_14 id=field_14 value=""></li>

       
      • TNTEverett

        TNTEverett - 2009-04-28

        Send me a copy of your form.html file.

         
    • justonemorequestion

      Kat,
      I was trying to develope file uploads also and gave up.
      1. How many submissions of artworks are you dealing with
      2.Were you able attribute images to individual artists to sort them.
      3.How you viewing the images after they are uploaded.

      Thanks.

       
    • Kathy Burch

      Kathy Burch - 2009-04-29

          <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
          <html>
          <head>
              <title>Artist Application - created by phpFormGenerator</title>
              <meta http-equiv="content-type" content="text/html; charset=UTF-8"><link href="style.css" rel="stylesheet" type="text/css">
              <!-- calendar stuff -->
                    <link rel="stylesheet" type="text/css" href="calendar/calendar-blue2.css" />
                    <script type="text/javascript" src="calendar/calendar.js"></script>
                    <script type="text/javascript" src="calendar/calendar-en.js"></script>
                    <script type="text/javascript" src="calendar/calendar-setup.js"></script>
              <!-- END calendar stuff -->

              <!-- expand/collapse function -->
              <SCRIPT type=text/javascript>
              <!--
              function collapseElem(obj)
              {
                  var el = document.getElementById(obj);
                  el.style.display = 'none';
              }

              function expandElem(obj)
              {
                  var el = document.getElementById(obj);
                  el.style.display = '';
              }

              //-->
              </SCRIPT>
              <!-- expand/collapse function -->

              <!-- expand/collapse function -->
                  <SCRIPT type=text/javascript>
                  <!--

                  // collapse all elements, except the first one
                  function collapseAll()
                  {
                      var numFormPages = 1;

                      for(i=2; i <= numFormPages; i++)
                      {
                          currPageId = ('mainForm_' + i);
                          collapseElem(currPageId);
                      }
                  }

                  //-->
                  </SCRIPT>
              <!-- expand/collapse function -->

               <!-- validate -->
              <SCRIPT type=text/javascript>
              <!--
                  function validateField(fieldId, fieldBoxId, fieldType, required)
                  {
                      fieldBox = document.getElementById(fieldBoxId);
                      fieldObj = document.getElementById(fieldId);

                      if(fieldType == 'text'  ||  fieldType == 'textarea'  ||  fieldType == 'password'  ||  fieldType == 'file'  ||  fieldType == 'phone'  || fieldType == 'website')
                      {   
                          if(required == 1 && fieldObj.value == '')
                          {
                              fieldObj.setAttribute("class","mainFormError");
                              fieldObj.setAttribute("className","mainFormError");
                              fieldObj.focus();
                              return false;                   
                          }

                      }

                      else if(fieldType == 'menu'  || fieldType == 'country'  || fieldType == 'state')
                      {   
                          if(required == 1 && fieldObj.selectedIndex == 0)
                          {               
                              fieldObj.setAttribute("class","mainFormError");
                              fieldObj.setAttribute("className","mainFormError");
                              fieldObj.focus();
                              return false;                   
                          }

                      }

                      else if(fieldType == 'email')
                      {   
                          if((required == 1 && fieldObj.value=='')  ||  (fieldObj.value!=''  && !validate_email(fieldObj.value)))
                          {               
                              fieldObj.setAttribute("class","mainFormError");
                              fieldObj.setAttribute("className","mainFormError");
                              fieldObj.focus();
                              return false;                   
                          }

                      }

                  }

                  function validate_email(emailStr)
                  {       
                      apos=emailStr.indexOf("@");
                      dotpos=emailStr.lastIndexOf(".");

                      if (apos<1||dotpos-apos<2)
                      {
                          return false;
                      }
                      else
                      {
                          return true;
                      }
                  }

                  function validateDate(fieldId, fieldBoxId, fieldType, required,  minDateStr, maxDateStr)
                  {
                      retValue = true;

                      fieldBox = document.getElementById(fieldBoxId);
                      fieldObj = document.getElementById(fieldId);   
                      dateStr = fieldObj.value;

                      if(required == 0  && dateStr == '')
                      {
                          return true;
                      }

                      if(dateStr.charAt(2) != '/'  || dateStr.charAt(5) != '/' || dateStr.length != 10)
                      {
                          retValue = false;
                      }   

                      else    // format's okay; check max, min
                      {
                          currDays = parseInt(dateStr.substr(0,2),10) + parseInt(dateStr.substr(3,2),10)*30  + parseInt(dateStr.substr(6,4),10)*365;
                          //alert(currDays);

                          if(maxDateStr != '')
                          {
                              maxDays = parseInt(maxDateStr.substr(0,2),10) + parseInt(maxDateStr.substr(3,2),10)*30  + parseInt(maxDateStr.substr(6,4),10)*365;
                              //alert(maxDays);
                              if(currDays > maxDays)
                                  retValue = false;
                          }

                          if(minDateStr != '')
                          {
                              minDays = parseInt(minDateStr.substr(0,2),10) + parseInt(minDateStr.substr(3,2),10)*30  + parseInt(minDateStr.substr(6,4),10)*365;
                              //alert(minDays);
                              if(currDays < minDays)
                                  retValue = false;
                          }
                      }

                      if(retValue == false)
                      {
                          fieldObj.setAttribute("class","mainFormError");
                          fieldObj.setAttribute("className","mainFormError");
                          fieldObj.focus();
                          return false;
                      }
                  }
              //-->
              </SCRIPT>
              <!-- end validate -->

              <style type="text/css">
      <!--
      .style2 {color: #8E494E}
      .style3 {    font-family: Arial, Helvetica, sans-serif;
          color: #FFFFFF;
      }
      -->
              </style>
          </head>

          <body onLoad="collapseAll()">

          <div id="mainForm">

              <div id="formHeader">
                      <h2 class="formInfo">Artist Application</h2>
                      <p class="formInfo">Application for consideration of work to be sold through Simply Divine! LLC</p>
              </div>

              *Indicates required field.&nbsp; <span class="mainForm"><a class=info href=#><img src=imgs/tip_small.png border=0></a></span>Mouse over for more information about that field. <BR/>
              <!-- begin form -->
              <form method=post enctype=multipart/form-data action=processor.php onSubmit="return validatePage1();"><ul class=mainForm id="mainForm_1">

                      <li class="mainForm" id="fieldBox_1">
                          <label class="formFieldQuestion">Name&nbsp;*&nbsp;<a class=info href=#><img src=imgs/tip_small.png border=0><span class=infobox>Your full name please.</span></a></label><input class=mainForm type=text name=field_1 id=field_1 size='35' value=''></li>

                      <li class="mainForm" id="fieldBox_2">
                          <label class="formFieldQuestion">Company Name (if applicable)&nbsp;<a class=info href=#><img src=imgs/tip_small.png border=0><span class=infobox>Name of company by which you sell your items.</span></a></label><input class=mainForm type=text name=field_2 id=field_2 size='35' value=''></li>

                      <li class="mainForm" id="fieldBox_3">
                          <label class="formFieldQuestion">Address&nbsp;*&nbsp;<a class=info href=#><img src=imgs/tip_small.png border=0><span class=infobox>Your street address</span></a></label><input class=mainForm type=text name=field_3 id=field_3 size='35' value=''></li>

                      <li class="mainForm" id="fieldBox_4">
                          <label class="formFieldQuestion">City&nbsp;*</label><input class=mainForm type=text name=field_4 id=field_4 size='35' value=''></li>

                      <li class="mainForm" id="fieldBox_5">
                          <label class="formFieldQuestion">State&nbsp;*</label><select class=mainForm name=field_5 id=field_5><option value=''> </option><option value="Alabama">Alabama</option><option value="Alaska">Alaska</option><option value="Arizona">Arizona</option><option value="Arkansas">Arkansas</option><option value="California">California</option><option value="Colorado">Colorado</option><option value="Connecticut">Connecticut</option><option value="Delaware">Delaware</option><option value="Florida">Florida</option><option value="Georgia">Georgia</option><option value="Hawaii">Hawaii</option><option value="Idaho">Idaho</option><option value="Illinois">Illinois</option><option value="Indiana">Indiana</option><option value="Iowa">Iowa</option><option value="Kansas">Kansas</option><option value="Kentucky">Kentucky</option><option value="Louisiana">Louisiana</option><option value="Maine">Maine</option><option value="Maryland">Maryland</option><option value="Massachusetts">Massachusetts</option><option value="Michigan">Michigan</option><option value="Minnesota">Minnesota</option><option value="Mississippi">Mississippi</option><option value="Missouri">Missouri</option><option value="Montana">Montana</option><option value="Nebraska">Nebraska</option><option value="Nevada">Nevada</option><option value="New Hampshire">New Hampshire</option><option value="New Jersey">New Jersey</option><option value="New Mexico">New Mexico</option><option value="New York">New York</option><option value="North Carolina">North Carolina</option><option value="North Dakota">North Dakota</option><option value="Ohio">Ohio</option><option value="Oklahoma">Oklahoma</option><option value="Oregon">Oregon</option><option value="Pennsylvania">Pennsylvania</option><option value="Rhode Island">Rhode Island</option><option value="South Carolina">South Carolina</option><option value="South Dakota">South Dakota</option><option value="Tennessee">Tennessee</option><option value="Texas">Texas</option><option value="Utah">Utah</option><option value="Vermont">Vermont</option><option value="Virginia">Virginia</option><option value="Washington">Washington</option><option value="West Virginia">West Virginia</option><option value="Wisconsin">Wisconsin</option><option value="Wyoming">Wyoming</option></SELECT></li>

                      <li class="mainForm" id="fieldBox_6">
                          <label class="formFieldQuestion">Zipcode&nbsp;*</label><input class=mainForm type=text name=field_6 id=field_6 size='15' value=''></li>

                      <li class="mainForm" id="fieldBox_7">
                          <label class="formFieldQuestion">Phone&nbsp;*&nbsp;<a class=info href=#><img src=imgs/tip_small.png border=0><span class=infobox>Please list all where you can be reached; i.e., cell, work, home, etc.</span></a></label><input class=mainForm type=phone name=field_7 id=field_7 size=50 value="" style="background-image:url(imgs/phone.png); background-repeat: no-repeat;  padding: 2px 2px 2px 25px;"></li>

                      <li class="mainForm" id="fieldBox_8">
                          <label class="formFieldQuestion">Email Address&nbsp;*</label><input class=mainForm type=email name=field_8 id=field_8 size=35 value="" style="background-image:url(imgs/email.png); background-repeat: no-repeat;  padding: 2px 2px 2px 25px;"></li>

                      <li class="mainForm" id="fieldBox_9">
                          <label class="formFieldQuestion">Website&nbsp;<a class=info href=#><img src=imgs/tip_small.png border=0><span class=infobox>Please list url of website where you have your work displayed if applicable.</span></a></label><input class=mainForm type=website name=field_9 id=field_9 size=35 value="" style="background-image:url(imgs/website.png); background-repeat: no-repeat;  padding: 2px 2px 2px 25px;"></li>

                      <li class="mainForm" id="fieldBox_10">
                          <label class="formFieldQuestion">Do you sell your work elsewhere? Check all that apply.&nbsp;*</label><span><input class=mainForm type=checkbox name=field_10[] id=field_10_option_1 value="None" />
                          <label class=formFieldOption for="field_10_option_1">None</label>
                          <input class=mainForm type=checkbox name=field_10[] id=field_10_option_2 value="Craft Shows" />
                          <label class=formFieldOption for="field_10_option_2">Craft Shows</label>
                          <input class=mainForm type=checkbox name=field_10[] id=field_10_option_3 value="Local Galleries/Shops" /><label class=formFieldOption for="field_10_option_3">Local Galleries/Shops</label><input class=mainForm type=checkbox name=field_10[] id=field_10_option_4 value="Galleries/Shops Outside Pittsburgh Area" /><label class=formFieldOption for="field_10_option_4">Galleries/Shops Outside Pittsburgh Area</label><input class=mainForm type=checkbox name=field_10[] id=field_10_option_5 value="In-home Parties" /><label class=formFieldOption for="field_10_option_5">In-home Parties</label><input class=mainForm type=checkbox name=field_10[] id=field_10_option_6 value="Ebay" /><label class=formFieldOption for="field_10_option_6">Ebay</label><input class=mainForm type=checkbox name=field_10[] id=field_10_option_7 value="Etsy.com" /><label class=formFieldOption for="field_10_option_7">Etsy.com</label><input class=mainForm type=checkbox name=field_10[] id=field_10_option_8 value="Your own Website" /><label class=formFieldOption for="field_10_option_8">Your own Website</label><input class=mainForm type=checkbox name=field_10[] id=field_10_option_9 value="Other Website(s)" /><label class=formFieldOption for="field_10_option_9">Other Website(s)</label><input class=mainForm type=checkbox name=field_10[] id=field_10_option_10 value="Other" /><label class=formFieldOption for="field_10_option_10">Other</label></span></li>

                      <li class="mainForm" id="fieldBox_11">
                          <label class="formFieldQuestion">List other local galleries or shops where you sell or display your work (include local craft and art shows in the Pittsburgh area)&nbsp;*</label><textarea class=mainForm  name=field_11 id=field_11 rows=25 cols=80></textarea></li>

                      <li class="mainForm" id="fieldBox_12">
                          <label class="formFieldQuestion">List internet sites where you sell, including Ebay if applicable (full url please)&nbsp;*</label><textarea class=mainForm  name=field_12 id=field_12 rows=20 cols=80></textarea></li>

                      <li class="mainForm" id="fieldBox_13">
                          <label class="formFieldQuestion">Average price point of your merchandise&nbsp;*</label><input class=mainForm type=text name=field_13 id=field_13 size='35' value=''></li>

                      <li class="mainForm" id="fieldBox_14">
                          <label class="formFieldQuestion">Photo of your work&nbsp;<a class=info href=#><img src=imgs/tip_small.png border=0><span class=infobox>Please include a photo of your work if available</span></a></label><input class=mainForm type=file name=field_14 id=field_14 value=""></li>

                      <li class="mainForm" id="fieldBox_15">
                          <label class="formFieldQuestion">Artist Statement &amp; Description of Work &nbsp;*&nbsp;<a class=info href=#><img src=imgs/tip_small.png border=0><span class=infobox>Please give a brief description of your work.</span></a></label><textarea class=mainForm  name=field_15 id=field_15 rows=10 cols=80></textarea></li>

                      <li class="mainForm" id="fieldBox_16">
                          <label class="formFieldQuestion">Artist Bio (include awards, merits, education, etc.)&nbsp;*</label><textarea class=mainForm  name=field_16 id=field_16 rows=80 cols=80></textarea></li>
             
             
              <!-- end of this page -->

              <!-- page validation -->
              <SCRIPT type=text/javascript>
              <!--
                  function validatePage1()
                  {
                      retVal = true;
                      if (validateField('field_1','fieldBox_1','text',1) == false)
      retVal=false;
      if (validateField('field_2','fieldBox_2','text',0) == false)
      retVal=false;
      if (validateField('field_3','fieldBox_3','text',1) == false)
      retVal=false;
      if (validateField('field_4','fieldBox_4','text',1) == false)
      retVal=false;
      if (validateField('field_5','fieldBox_5','state',1) == false)
      retVal=false;
      if (validateField('field_6','fieldBox_6','text',1) == false)
      retVal=false;
      if (validateField('field_7','fieldBox_7','phone',1) == false)
      retVal=false;
      if (validateField('field_8','fieldBox_8','email',1) == false)
      retVal=false;
      if (validateField('field_9','fieldBox_9','website',0) == false)
      retVal=false;
      if (validateField('field_10','fieldBox_10','checkbox',1) == false)
      retVal=false;
      if (validateField('field_11','fieldBox_11','textarea',1) == false)
      retVal=false;
      if (validateField('field_12','fieldBox_12','textarea',1) == false)
      retVal=false;
      if (validateField('field_13','fieldBox_13','text',1) == false)
      retVal=false;
      if (validateField('field_14','fieldBox_14','file',0) == false)
      retVal=false;
      if (validateField('field_15','fieldBox_15','textarea',1) == false)
      retVal=false;
      if (validateField('field_16','fieldBox_16','textarea',1) == false)
      retVal=false;

                      if(retVal == false)
                      {
                          alert('Please correct the errors.  Fields marked with an asterisk (*) are required');
                          return false;
                      }
                      return retVal;
                  }
              //-->
              </SCRIPT>

              <!-- end page validaton -->

              <!-- next page buttons --><li class="mainForm">
                                      <label class="formFieldQuestion">
                                          Type the following:&nbsp;<a class=info href=#><img src=imgs/tip_small.png border=0><span class=infobox>For security purposes, please type the letters in the image.</span></a><BR><img src="CaptchaSecurityImages.php" />
                                      </label>

                                      <input id="captchaForm" name="security_code" class="mainForm" type="text"/>
                                  </li><li class="mainForm">
                          <input id="saveForm" class="mainForm" type="submit" value="Submit" />
                      </li>

            </form>
                  <!-- end of form -->
              <!-- close the display stuff for this page -->
              </ul></div>
          <div id="footer"><p class="footer"><a class=footer href=http://phpformgen.sourceforge.net>Generated by phpFormGenerator</a></p>
            <p>&nbsp;</p>
            <p align="center" class="style3"><span class="style2 style3"><a href="../index.html">HOME</a> </span></p>
            <p align="center" class="style3">
            <h5 align="center"><span class="style2"><span class="style3">Copyright &copy; Simply Divine!, LLC<SUP>&#153;</SUP></span><SUP></SUP><FONT FACE="Arial"><SUP><br>
            </SUP></FONT> <A HREF="http://computrends.com" TARGET="_blank"><IMG SRC="http://computrends.com/images/geolgsitedeslogo.jpg"
                ALT="Web Design by CompuTrends" WIDTH="204" HEIGHT="53" BORDER="0"></A> </span></h5>
            <p>&nbsp;</p>
          </div>

          </body>
          </html>

       
    • Kathy Burch

      Kathy Burch - 2009-04-29

      We only get a few artist applications in any given month and only allowed for one photo to be uploaded.  But the hackers were uploading a php file that activated another script that wiped out our home page.  Luckily it was ONLY our home page and not the whole site.  So I was able to get it back online easily enough.  I'm not sure it's worth even having this capability if it can allow hackers in.  Might be easier to just ask them to email me a photo.  Do you think this can be done securely?

      Thanks for any help.  I have since removed the photo option from the site until I can get this rectified.

      Kathy

       
    • Musawir Ali

      Musawir Ali - 2009-04-29

      Even if you allow .php files to be uploaded, no one should be able to execute them. The files that are uploaded don't have (or at least, SHOULDN"T HAVE) execute permissions. Clearly in your case the uploaded files were in fact executable. This shows that your webhosting provider is not doing a proper job at administration.

      Although you can modify the form to not allow upload of any php files, you shouldn't really need to do that. The proper steps to fixing this problem are:

      1) make sure that uploaded files only have read access by default. Something like mode 744 should be fine. Under no circumstances should the last two digits be odd (such as 755, 733, 711, 777).

      2) make sure that an uploaded file is not owned by the same user as the account it is uploaded from. So in case an executable file is somehow uploaded (which should not happen if (1) has been implemented), it will not have the proper permissions to mess with any of your existing files.

      These are security policies that webserver administrators implement. If this is not the case with your server, you should bring it to your admin's attention.

       

Log in to post a comment.

Want the latest updates on software, tech news, and AI?
Get latest updates about software, tech news, and AI from SourceForge directly in your inbox once a month.