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.