Securing your PayPal IPN script
When writing your first IPN script to handle post-backs from PayPal in order to simplify or automate your website, it is easy to miss a couple of major security issues with regards to this.
The first thing you must do when receiving POST data on your IPN script, is to reverse everything and submit a VERIFY request to paypal.com . If PayPal says the transaction did not occur, then most likely, the transaction is an attempt to exploit a common hole in IPN scripts which allow anyone to submit well formed IPN data in the amount desired in order to fake a transaction. Without VERIFY, your script will simply assume the transaction was valid, and process the users membership/purchase as such — even when no money ever went into your PayPal account.
The problem with the above, is that if someone is especially malicious, you could end up DoSing yourself, or possibly PayPal (depending on your bandwidth level). Since all of PayPal / eBay’s IP’s are registered with ARIN, you can implement the following into your .htaccess file and save on bandwidth, thus ensuring only callbacks to PayPal will occur when a transaction appears to be coming from a PayPal/eBay assigned IP address. You should still use the VERIFY command as this still leaves you open for attacks from anyone on the PayPal/eBay network, or, if someone is really good and manages to bypass all .htaccess files on your system using another exploit — in which case, your server is probably a sloppy configuration anyway.
HTACCESS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 | Options -Indexes <Files ipn.php> order deny, allow deny from all allow 63.168.4. allow 208.14.218.16 allow 208.14.218.17 allow 208.14.218.18 allow 208.14.218.19 allow 208.14.218.20 allow 208.14.218.21 allow 208.14.218.22 allow 208.14.218.23 allow 208.14.218.24 allow 208.14.218.25 allow 208.14.218.26 allow 208.14.218.27 allow 208.14.218.28 allow 208.14.218.29 allow 208.14.218.30 allow 208.14.218.31 allow 208.34.52. allow 208.34.53. allow 208.34.54. allow 208.34.55. allow 65.168.176.224 allow 65.168.176.225 allow 65.168.176.226 allow 65.168.176.227 allow 65.168.176.228 allow 65.168.176.229 allow 65.168.176.230 allow 65.168.176.231 allow 65.168.176.232 allow 65.168.176.233 allow 65.168.176.234 allow 65.168.176.235 allow 65.168.176.236 allow 65.168.176.237 allow 65.168.176.238 allow 65.168.176.239 allow 66.135.192. allow 66.135.193. allow 66.135.194. allow 66.135.195. allow 66.135.196. allow 66.135.197. allow 66.135.198. allow 66.135.199. allow 66.135.200. allow 66.135.201. allow 66.135.202. allow 66.135.203. allow 66.135.204. allow 66.135.205. allow 66.135.206. allow 66.135.207. allow 66.135.208. allow 66.135.209. allow 66.135.210. allow 66.135.211. allow 66.135.212. allow 66.135.213. allow 66.135.214. allow 66.135.215. allow 66.135.216. allow 66.135.217. allow 66.135.218. allow 66.135.219. allow 66.135.220. allow 66.135.221. allow 66.135.222. allow 66.135.223. allow 64.68.78. allow 64.68.79. allow 63.171.24.16 allow 63.171.24.17 allow 63.171.24.18 allow 63.171.24.19 allow 63.171.24.20 allow 63.171.24.21 allow 63.171.24.22 allow 63.171.24.23 allow 63.171.24.24 allow 63.171.24.25 allow 63.171.24.26 allow 63.171.24.27 allow 63.171.24.28 allow 63.171.24.29 allow 63.171.24.30 allow 63.171.24.31 allow 198.69.206.176 allow 198.69.206.177 allow 198.69.206.178 allow 198.69.206.179 allow 198.69.206.180 allow 198.69.206.181 allow 198.69.206.182 allow 198.69.206.183 allow 198.69.206.184 allow 198.69.206.185 allow 198.69.206.186 allow 198.69.206.187 allow 198.69.206.188 allow 198.69.206.189 allow 198.69.206.190 allow 198.69.206.191 allow 216.113.160. allow 216.113.161. allow 216.113.162. allow 216.113.163. allow 216.113.164. allow 216.113.165. allow 216.113.166. allow 216.113.167. allow 216.113.168. allow 216.113.169. allow 216.113.170. allow 216.113.171. allow 216.113.172. allow 216.113.173. allow 216.113.174. allow 216.113.175. allow 216.113.176. allow 216.113.177. allow 216.113.178. allow 216.113.179. allow 216.113.180. allow 216.113.181. allow 216.113.182. allow 216.113.183. allow 216.113.184. allow 216.113.185. allow 216.113.186. allow 216.113.187. allow 216.113.188. allow 216.113.189. allow 216.113.190. allow 216.113.191. allow 64.4.240. allow 64.4.241. allow 64.4.242. allow 64.4.243. allow 64.4.244. allow 64.4.245. allow 64.4.246. allow 64.4.247. allow 64.4.248. allow 64.4.249. allow 64.4.250. allow 64.4.251. allow 64.4.252. allow 64.4.253. allow 64.4.254. allow 64.4.255. allow 173.0.80. allow 173.0.81. allow 173.0.82. allow 173.0.83. allow 173.0.84. allow 173.0.85. allow 173.0.86. allow 173.0.87. allow 173.0.88. allow 173.0.89. allow 173.0.90. allow 173.0.91. allow 173.0.92. allow 173.0.93. allow 173.0.94. allow 173.0.95. </Files> |
These blocks are based on the IP addresses currently assigned to eBay and to PayPal at the time of writing this.
PHP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 | /* Assign the network blocks and ranges For example: 63.168.4.0 - 63.168.4.255 Would be array( 'group' => '63.168.4', 'start' => 0, 'end' => 255 ) */ $networks = array( array( 'group' => '63.168.4', 'start' => 0, 'end' => 255 ), array( 'group' => '208.14.218', 'start' => 16, 'end' => 31 ), array( 'group' => '208.34', 'start' => 52, 'end' => 55 ), array( 'group' => '65.168.176', 'start' => 224, 'end' => 239 ), array( 'group' => '66.135', 'start' => 192, 'end' => 223 ), array( 'group' => '64.68', 'start' => 78, 'end' => 79 ), array( 'group' => '63.171.24', 'start' => 16, 'end' => 31 ), array( 'group' => '198.69.206', 'start' => 176, 'end' => 191 ), array( 'group' => '216.113', 'start' => 160, 'end' => 191 ), array( 'group' => '64.4', 'start' => 240, 'end' => 255 ), array( 'group' => '173.0', 'start' => 80, 'end' => 95 ) ); // used for output of .htaccess rules $htaccess = "<files ipn.php>\n" . " order deny, allow\n" . " deny from all\n"; // loop through networks and build rules for each range foreach( $networks as $key=>$network ) { $htaccess .= ip( $network['group'], $network['start'], $network['end'] ); } $htaccess .= "</files>"; // output results to screen echo "<pre>{$htaccess}</pre>"; // helper method for building a list of ip blocks from subranges of ip blocks function ip($point = '127.0.0', $start = 10, $end = 20, $rule = 'allow') { $x = $start; $result = ''; if($start == 0 && $end == 255) $result .= " {$rule} {$point}" . ( count(explode('.',$point)) < 4 ? ".\n" : "\n"; return $result; } while($x <= $end) { $result .= " {$rule} {$point}.{$x}" . ( count(explode('.', "{$point}.{$x}")) < 4 ? ".\n" : "\n"; $x++; } return $result; } |
This small PHP script assisted in building the blocks as you see them above in HTACCESS.