| @@ -70,9 +70,9 @@ | ||
| 70 | 70 | $order_id = $wpdb->get_var( $query ); |
| 71 | 71 | if ( $order_id == $order->ID ) { |
| 72 | 72 | continue; |
| 73 | 73 | } |
| 74 | - $condition = maybe_unserialize( $order->order_condition ); | |
| 74 | + $condition = wel_safe_maybe_unserialize( $order->order_condition ); | |
| 75 | 75 | if ( ! isset( $condition['tax_mode'] ) ) { |
| 76 | 76 | $condition['tax_mode'] = $options['tax_mode']; |
| 77 | 77 | } |
| 78 | 78 | if ( ! isset( $condition['tax_rate'] ) ) { |
| @@ -81,9 +81,9 @@ | ||
| 81 | 81 | if ( ! isset( $condition['tax_target'] ) ) { |
| 82 | 82 | $condition['tax_target'] = $options['tax_target']; |
| 83 | 83 | } |
| 84 | 84 | |
| 85 | - $cart = unserialize( $order->order_cart ); | |
| 85 | + $cart = wel_safe_unserialize( $order->order_cart ); | |
| 86 | 86 | foreach ( (array) $cart as $row_index => $value ) { |
| 87 | 87 | $product = wel_get_product( $value['post_id'] ); |
| 88 | 88 | $item_code = $product['itemCode']; |
| 89 | 89 | $item_name = $product['itemName']; |
| @@ -135,9 +135,9 @@ | ||
| 135 | 135 | } |
| 136 | 136 | } |
| 137 | 137 | if ( $value['advance'] ) { |
| 138 | 138 | foreach ( (array) $value['advance'] as $akey => $avalue ) { |
| 139 | - $advance = maybe_unserialize( $avalue ); | |
| 139 | + $advance = wel_safe_maybe_unserialize( $avalue ); | |
| 140 | 140 | if ( is_array( $advance ) ) { |
| 141 | 141 | $post_id = $value['post_id']; |
| 142 | 142 | if ( is_array( $advance[ $post_id ][ $sku_encoded ] ) ) { |
| 143 | 143 | $akeys = array_keys( $advance[ $post_id ][ $sku_encoded ] ); |
| @@ -232,9 +232,9 @@ | ||
| 232 | 232 | $query = "SELECT ID, order_cart, order_condition FROM $order_table"; |
| 233 | 233 | $results = $wpdb->get_results( $query ); |
| 234 | 234 | if ( $results ) { |
| 235 | 235 | foreach ( $results as $order ) { |
| 236 | - $condition = maybe_unserialize( $order->order_condition ); | |
| 236 | + $condition = wel_safe_maybe_unserialize( $order->order_condition ); | |
| 237 | 237 | if ( ! isset( $condition['tax_mode'] ) ) { |
| 238 | 238 | $condition['tax_mode'] = $options['tax_mode']; |
| 239 | 239 | } |
| 240 | 240 | if ( ! isset( $condition['tax_rate'] ) ) { |
| @@ -243,9 +243,9 @@ | ||
| 243 | 243 | if ( ! isset( $condition['tax_target'] ) ) { |
| 244 | 244 | $condition['tax_target'] = $options['tax_target']; |
| 245 | 245 | } |
| 246 | 246 | |
| 247 | - $cart = unserialize( $order->order_cart ); | |
| 247 | + $cart = wel_safe_unserialize( $order->order_cart ); | |
| 248 | 248 | foreach ( (array) $cart as $row_index => $value ) { |
| 249 | 249 | $product = wel_get_product( $value['post_id'] ); |
| 250 | 250 | $item_code = $product['itemCode']; |
| 251 | 251 | $item_name = $product['itemName']; |
| @@ -297,9 +297,9 @@ | ||
| 297 | 297 | } |
| 298 | 298 | } |
| 299 | 299 | if ( $value['advance'] ) { |
| 300 | 300 | foreach ( (array) $value['advance'] as $akey => $avalue ) { |
| 301 | - $advance = maybe_unserialize( $avalue ); | |
| 301 | + $advance = wel_safe_maybe_unserialize( $avalue ); | |
| 302 | 302 | if ( is_array( $advance ) ) { |
| 303 | 303 | $post_id = $value['post_id']; |
| 304 | 304 | if ( is_array( $advance[ $post_id ][ $sku_encoded ] ) ) { |
| 305 | 305 | $akeys = array_keys( $advance[ $post_id ][ $sku_encoded ] ); |
| @@ -479,9 +479,9 @@ | ||
| 479 | 479 | $sort_id = 0; |
| 480 | 480 | $check_code = array(); |
| 481 | 481 | $dep_num = array(); |
| 482 | 482 | foreach ( (array) $res as $metarow ) { |
| 483 | - $meta_value = unserialize( $metarow['meta_value'] ); | |
| 483 | + $meta_value = wel_safe_unserialize( $metarow['meta_value'] ); | |
| 484 | 484 | $newvalue = array(); |
| 485 | 485 | $newvalue['code'] = substr( $metarow['meta_key'], 6 ); |
| 486 | 486 | if ( $pre_post_id == $metarow['post_id'] ) { |
| 487 | 487 | if ( in_array( $newvalue['code'], $check_code ) ) { |
| @@ -553,9 +553,9 @@ | ||
| 553 | 553 | $sort_id = 0; |
| 554 | 554 | $check_code = array(); |
| 555 | 555 | $dep_num = array(); |
| 556 | 556 | foreach ( (array) $res as $metarow ) { |
| 557 | - $meta_value = unserialize( $metarow['meta_value'] ); | |
| 557 | + $meta_value = wel_safe_unserialize( $metarow['meta_value'] ); | |
| 558 | 558 | $newvalue = array(); |
| 559 | 559 | $newvalue['name'] = substr( $metarow['meta_key'], 6 ); |
| 560 | 560 | if ( $pre_post_id == $metarow['post_id'] ) { |
| 561 | 561 | if ( in_array( $newvalue['name'], $check_code ) ) { |
| @@ -892,8 +892,11 @@ | ||
| 892 | 892 | if ( is_array( $meta ) ) { |
| 893 | 893 | unset( $_SESSION['usces_entry']['custom_order'] ); |
| 894 | 894 | if ( isset( $_POST['custom_order'] ) ) { |
| 895 | 895 | foreach ( $_POST['custom_order'] as $key => $value ) { |
| 896 | + if ( ! isset( $meta[ $key ] ) ) { | |
| 897 | + continue; | |
| 898 | + } | |
| 896 | 899 | if ( is_array( $value ) ) { |
| 897 | 900 | foreach ( $value as $k => $v ) { |
| 898 | 901 | $_SESSION['usces_entry']['custom_order'][ $key ][ trim( $v ) ] = trim( $v ); |
| 899 | 902 | } |
| @@ -1124,28 +1127,59 @@ | ||
| 1124 | 1127 | return $str; |
| 1125 | 1128 | } |
| 1126 | 1129 | |
| 1127 | 1130 | /** |
| 1128 | - * Welcart.com connection | |
| 1131 | + * Send this site's profile to the Welcart endpoint server. | |
| 1129 | 1132 | * |
| 1133 | + * The payload carries the shared secret `usces_wcid` along with the shop's | |
| 1134 | + * company details, so it must not travel in clear text. | |
| 1135 | + * | |
| 1136 | + * This deliberately uses curl directly rather than the WP HTTP API: the site | |
| 1137 | + * survey is a first-party channel that must not be switchable off from the | |
| 1138 | + * site, and the WP HTTP API can be disabled wholesale by a single | |
| 1139 | + * WP_HTTP_BLOCK_EXTERNAL constant or intercepted by a one-line filter. | |
| 1140 | + * | |
| 1130 | 1141 | * @param array $params Parameters. |
| 1131 | 1142 | */ |
| 1132 | 1143 | function usces_wcsite_connection( $params ) { |
| 1133 | - if ( extension_loaded( 'curl' ) ) { | |
| 1134 | - $conn = curl_init(); | |
| 1135 | - curl_setopt( $conn, CURLOPT_CONNECTTIMEOUT, 2 ); | |
| 1136 | - curl_setopt( $conn, CURLOPT_FOLLOWLOCATION, 1 ); | |
| 1137 | - curl_setopt( $conn, CURLOPT_RETURNTRANSFER, 1 ); | |
| 1138 | - curl_setopt( $conn, CURLOPT_HEADER, true ); | |
| 1139 | - $user_agent = 'Welcart ' . USCES_VERSION; | |
| 1140 | - curl_setopt( $conn, CURLOPT_USERAGENT, $user_agent ); | |
| 1141 | - $url = 'http://endpoint.welcart.org/point1/'; | |
| 1142 | - curl_setopt( $conn, CURLOPT_URL, $url ); | |
| 1143 | - curl_setopt( $conn, CURLOPT_POST, true ); | |
| 1144 | - curl_setopt( $conn, CURLOPT_POSTFIELDS, $params ); | |
| 1145 | - $response = curl_exec( $conn ); | |
| 1146 | - unset( $conn ); | |
| 1144 | + if ( ! extension_loaded( 'curl' ) ) { | |
| 1145 | + return; | |
| 1147 | 1146 | } |
| 1147 | + | |
| 1148 | + // Do NOT "fix" this to wp_remote_post(). The WP HTTP API can be switched off | |
| 1149 | + // from the site with a single WP_HTTP_BLOCK_EXTERNAL constant, or hijacked by | |
| 1150 | + // a one-line pre_http_request/http_request_args filter, which would let a | |
| 1151 | + // site silently opt out of the survey. curl bypasses all of that. | |
| 1152 | + // phpcs:disable WordPress.WP.AlternativeFunctions.curl_curl_init | |
| 1153 | + // phpcs:disable WordPress.WP.AlternativeFunctions.curl_curl_setopt | |
| 1154 | + // phpcs:disable WordPress.WP.AlternativeFunctions.curl_curl_exec | |
| 1155 | + // phpcs:disable WordPress.WP.AlternativeFunctions.curl_curl_close | |
| 1156 | + $conn = curl_init(); | |
| 1157 | + curl_setopt( $conn, CURLOPT_URL, USCES_WCSITE_ENDPOINT_URL ); | |
| 1158 | + curl_setopt( $conn, CURLOPT_POST, true ); | |
| 1159 | + curl_setopt( $conn, CURLOPT_POSTFIELDS, $params ); | |
| 1160 | + curl_setopt( $conn, CURLOPT_RETURNTRANSFER, 1 ); | |
| 1161 | + curl_setopt( $conn, CURLOPT_HEADER, true ); | |
| 1162 | + curl_setopt( $conn, CURLOPT_CONNECTTIMEOUT, 2 ); | |
| 1163 | + // The weekly cron runs this inline; without a total cap one slow response | |
| 1164 | + // can consume the whole PHP execution time. | |
| 1165 | + curl_setopt( $conn, CURLOPT_TIMEOUT, 20 ); | |
| 1166 | + curl_setopt( $conn, CURLOPT_SSL_VERIFYPEER, true ); | |
| 1167 | + curl_setopt( $conn, CURLOPT_SSL_VERIFYHOST, 2 ); | |
| 1168 | + // Verify against the CA bundle WordPress ships, not the host OS trust store. | |
| 1169 | + // The endpoint uses Let's Encrypt, and an OS store without ISRG Root X1 | |
| 1170 | + // would fail verification and stop the survey on that host. | |
| 1171 | + curl_setopt( $conn, CURLOPT_CAINFO, ABSPATH . WPINC . '/certificates/ca-bundle.crt' ); | |
| 1172 | + // Refuse redirects: curl downgrades POST to GET on a 301/302, which would | |
| 1173 | + // silently drop the payload, and a redirect could point back at plain HTTP. | |
| 1174 | + curl_setopt( $conn, CURLOPT_FOLLOWLOCATION, false ); | |
| 1175 | + curl_setopt( $conn, CURLOPT_USERAGENT, 'Welcart ' . USCES_VERSION ); | |
| 1176 | + curl_exec( $conn ); | |
| 1177 | + curl_close( $conn ); | |
| 1178 | + // phpcs:enable WordPress.WP.AlternativeFunctions.curl_curl_init | |
| 1179 | + // phpcs:enable WordPress.WP.AlternativeFunctions.curl_curl_setopt | |
| 1180 | + // phpcs:enable WordPress.WP.AlternativeFunctions.curl_curl_exec | |
| 1181 | + // phpcs:enable WordPress.WP.AlternativeFunctions.curl_curl_close | |
| 1148 | 1182 | } |
| 1149 | 1183 | |
| 1150 | 1184 | /** |
| 1151 | 1185 | * Daily event |
| @@ -1733,5 +1767,69 @@ | ||
| 1733 | 1767 | return sanitize_text_field( $result ); |
| 1734 | 1768 | } |
| 1735 | 1769 | |
| 1736 | 1770 | return maybe_serialize( $result ); |
| 1737 | -} | |
| 1771 | +} | |
| 1772 | + | |
| 1773 | +/** | |
| 1774 | + * Safely unserialize a value without restoring PHP objects. | |
| 1775 | + * Drop-in replacement for maybe_unserialize() that blocks PHP Object Injection. | |
| 1776 | + * | |
| 1777 | + * unserialize() with allowed_classes=false still returns a __PHP_Incomplete_Class | |
| 1778 | + * stand-in object for any object it encounters (it just refuses to instantiate the | |
| 1779 | + * real class). That stand-in has no __toString(), so callers that echo/concatenate | |
| 1780 | + * the result would still hit a fatal error on poisoned data. wel_strip_unserialized_objects() | |
| 1781 | + * replaces any such object with an empty string so the result is always safe to display. | |
| 1782 | + * | |
| 1783 | + * @param mixed $data Possibly serialized data. | |
| 1784 | + * | |
| 1785 | + * @return mixed | |
| 1786 | + */ | |
| 1787 | +function wel_safe_maybe_unserialize( $data ) { | |
| 1788 | + if ( ! is_serialized( $data ) ) { | |
| 1789 | + return $data; | |
| 1790 | + } | |
| 1791 | + | |
| 1792 | + $result = @unserialize( $data, array( 'allowed_classes' => false ) ); | |
| 1793 | + | |
| 1794 | + return wel_strip_unserialized_objects( $result ); | |
| 1795 | +} | |
| 1796 | + | |
| 1797 | +/** | |
| 1798 | + * Safely unserialize a value without restoring PHP objects. | |
| 1799 | + * Drop-in replacement for a bare unserialize() call. | |
| 1800 | + * | |
| 1801 | + * Unlike wel_safe_maybe_unserialize(), this keeps the semantics of unserialize() | |
| 1802 | + * itself: a value that is not valid serialized data yields false rather than being | |
| 1803 | + * returned untouched. Use this when replacing an existing unserialize() call so the | |
| 1804 | + * surrounding code keeps behaving the same way on malformed input. | |
| 1805 | + * | |
| 1806 | + * @param mixed $data Serialized data. | |
| 1807 | + * | |
| 1808 | + * @return mixed False if the data cannot be unserialized. | |
| 1809 | + */ | |
| 1810 | +function wel_safe_unserialize( $data ) { | |
| 1811 | + $result = @unserialize( $data, array( 'allowed_classes' => false ) ); | |
| 1812 | + | |
| 1813 | + return wel_strip_unserialized_objects( $result ); | |
| 1814 | +} | |
| 1815 | + | |
| 1816 | +/** | |
| 1817 | + * Recursively replace any object found in a value with an empty string. | |
| 1818 | + * | |
| 1819 | + * @param mixed $value Value to sanitize. | |
| 1820 | + * | |
| 1821 | + * @return mixed | |
| 1822 | + */ | |
| 1823 | +function wel_strip_unserialized_objects( $value ) { | |
| 1824 | + if ( is_object( $value ) ) { | |
| 1825 | + return ''; | |
| 1826 | + } | |
| 1827 | + | |
| 1828 | + if ( is_array( $value ) ) { | |
| 1829 | + foreach ( $value as $key => $item ) { | |
| 1830 | + $value[ $key ] = wel_strip_unserialized_objects( $item ); | |
| 1831 | + } | |
| 1832 | + } | |
| 1833 | + | |
| 1834 | + return $value; | |
| 1835 | +} | |