When we discovered the successful attack on ftp.horde.org two weeks ago we were of course frantic to determine which packages had been affected in addition to the one Horde 3 archive Jan identified as modified initially.
For the Horde 4 packages we had no hashes to verify the file integrity though. While PEAR supports signing of packages via GPG that seems to be a feature which is virtually unused. For one thing not that many PHP based projects use PEAR packaging and in addition there is no way to automatically verify package integrity on the user side when installing via PEAR. So we also didn't consider signing our packages when switching to installing Horde via PEAR.
Obviously you gain a different perspective on that issue once a hacker implanted a backdoor in some of your packages. Of course we invested a lot of time into securing our infrastructure now to ensure that such an event never happens again. On our side the file integrity is constantly monitored now. But we will also have to investigate how we can improve the PEAR based installation procedure so that it also allows for the required amount of security on the user side.
But if we had no hashes how did we ensure the Horde 4 packages were indeed unmodified? Git to the rescue! As we tag all our releases it was a matter of creating a short script to automatically compare the current state of the packages on our PEAR server against the state we had within git.
Without further ado - here is the script I used:
#!/bin/bash git reset --hard HEAD git clean -f -d STAMP=`date +%y%m%d-%H%M` mkdir ../diffs-$STAMP mkdir -p ../validate-$STAMP/pear.horde.org mkdir -p ../validate-$STAMP/rebuild for package in `cat ../pear-recovery-packages.txt | grep -v ".tar$"` do TAG=${package/.tgz/} TAG=${TAG,,} PPATH=${package/-*/} if [ "x${PPATH/Horde_*/}" == "x" ]; then PPATH=framework/${PPATH/Horde_}; fi if [ "x${PPATH/groupware*/}" == "x" ]; then PPATH=bundles/$PPATH; fi if [ "x${PPATH/webmail*/}" == "x" ]; then PPATH=bundles/$PPATH; fi PRESENT=`git tag -l $TAG` if [ "x$PRESENT" == "x" ]; then echo echo "======================================================================" echo "Tag $TAG for package $package is missing!" echo "======================================================================" echo echo "$package: TAG MISSING" >> ../status-$STAMP else rm *.tgz rm -rf ../validate-$STAMP/pear.horde.org/* rm -rf ../validate-$STAMP/rebuild/* GIT=`git checkout $TAG` horde-components -z $PPATH --keep-version if [ -e $package ]; then cp *.tgz ../validate-$STAMP/pear.horde.org/ cp ../pear.horde.org/get/$package ../validate-$STAMP/rebuild/ tar -C ../validate-$STAMP/pear.horde.org/ -x -z -f ../validate-$STAMP/pear.horde.org/*.tgz tar -C ../validate-$STAMP/rebuild/ -x -z -f ../validate-$STAMP/rebuild/*.tgz DIFF=`diff -Naur ../validate-$STAMP/pear.horde.org/${package/.tgz/} ../validate-$STAMP/rebuild/${package/.tgz/}` if [ "x$DIFF" != "x" ]; then echo echo "======================================================================" echo "Diff for package $package detected!" diff -Naur ../validate-$STAMP/pear.horde.org/${package/.tgz/} ../validate-$STAMP/rebuild/${package/.tgz/} > ..$ echo "======================================================================" echo echo "$package: DIFF" >> ../status-$STAMP else echo echo "======================================================================" echo "$package CLEAN!!!" echo "======================================================================" echo echo "$package: CLEAN" >> ../status-$STAMP fi else echo echo "======================================================================" echo "Failed rebuilding package $package!" echo "======================================================================" echo echo "$package: FAILED REBUILDING" >> ../status-$STAMP fi fi done
The script walks through the list of packages we had on the PEAR
server, moves back in time within our git repository to the
appropriate tag, rebundles the package, extracts both the old and the
new package and compares them using diff.
The resulting status list looked quite okay. There were a few release glitches where the tag was not on the exact commit that was used for building the package. In that case usually a small diff resulted. The list was rechecked manually for any malicious traces - Horde_Imap_Client-1.4.3 was the only one were the diff was large but that turned out to be a result from a mishap while releasing that package version. A few times the diff led to a problem with rebuilding the package (indicated by "FAILED REBUILDING"). The package.xml was broken in those cases and needed a fix that was only committed after the tagging. Here I compared the git state directly against the extracted package. There was only a single case ("Horde_ActiveSync-1.1.11") where the tag was missing which required manually identifying the corresponding commit to verify that state against the old package contents.
Once the script was established the analysis ran for about two hours after which we were at least somewhat relieved. Having a backdoor in some Horde 3 packages was already bad enough - having that in Horde 4 would have been even more disastrous.